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-2014 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
26 % http://www.imagemagick.org/script/license.php %
28 % Unless required by applicable law or agreed to in writing, software %
29 % distributed under the License is distributed on an "AS IS" BASIS, %
30 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31 % See the License for the specific language governing permissions and %
32 % limitations under the License. %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
43 #include "MagickCore/studio.h"
44 #include "MagickCore/artifact.h"
45 #include "MagickCore/attribute.h"
46 #include "MagickCore/cache-view.h"
47 #include "MagickCore/channel.h"
48 #include "MagickCore/client.h"
49 #include "MagickCore/color.h"
50 #include "MagickCore/color-private.h"
51 #include "MagickCore/colorspace.h"
52 #include "MagickCore/colorspace-private.h"
53 #include "MagickCore/compare.h"
54 #include "MagickCore/composite-private.h"
55 #include "MagickCore/constitute.h"
56 #include "MagickCore/exception-private.h"
57 #include "MagickCore/geometry.h"
58 #include "MagickCore/image-private.h"
59 #include "MagickCore/list.h"
60 #include "MagickCore/log.h"
61 #include "MagickCore/memory_.h"
62 #include "MagickCore/monitor.h"
63 #include "MagickCore/monitor-private.h"
64 #include "MagickCore/option.h"
65 #include "MagickCore/pixel-accessor.h"
66 #include "MagickCore/property.h"
67 #include "MagickCore/resource_.h"
68 #include "MagickCore/string_.h"
69 #include "MagickCore/statistic.h"
70 #include "MagickCore/thread-private.h"
71 #include "MagickCore/transform.h"
72 #include "MagickCore/utility.h"
73 #include "MagickCore/version.h"
76 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
80 % C o m p a r e I m a g e %
84 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
86 % CompareImages() compares one or more pixel channels of an image to a
87 % reconstructed image and returns the difference image.
89 % The format of the CompareImages method is:
91 % Image *CompareImages(const Image *image,const Image *reconstruct_image,
92 % const MetricType metric,double *distortion,ExceptionInfo *exception)
94 % A description of each parameter follows:
98 % o reconstruct_image: the reconstruct image.
100 % o metric: the metric.
102 % o distortion: the computed distortion between the images.
104 % o exception: return any errors or warnings in this structure.
108 static size_t GetImageChannels(const Image *image)
117 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
119 PixelChannel channel=GetPixelChannelChannel(image,i);
120 PixelTrait traits=GetPixelChannelTraits(image,channel);
121 if ((traits & UpdatePixelTrait) != 0)
124 return(channels == 0 ? 1 : channels);
127 static inline MagickBooleanType ValidateImageMorphology(
128 const Image *restrict image,const Image *restrict reconstruct_image)
131 Does the image match the reconstructed image morphology?
136 MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
137 const MetricType metric,double *distortion,ExceptionInfo *exception)
161 assert(image != (Image *) NULL);
162 assert(image->signature == MagickSignature);
163 if (image->debug != MagickFalse)
164 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
165 assert(reconstruct_image != (const Image *) NULL);
166 assert(reconstruct_image->signature == MagickSignature);
167 assert(distortion != (double *) NULL);
169 if (image->debug != MagickFalse)
170 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
171 if (metric != PerceptualHashErrorMetric)
172 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
173 ThrowImageException(ImageError,"ImageMorphologyDiffers");
174 status=GetImageDistortion(image,reconstruct_image,metric,distortion,
176 if (status == MagickFalse)
177 return((Image *) NULL);
178 difference_image=CloneImage(image,0,0,MagickTrue,exception);
179 if (difference_image == (Image *) NULL)
180 return((Image *) NULL);
181 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception);
182 highlight_image=CloneImage(image,image->columns,image->rows,MagickTrue,
184 if (highlight_image == (Image *) NULL)
186 difference_image=DestroyImage(difference_image);
187 return((Image *) NULL);
189 status=SetImageStorageClass(highlight_image,DirectClass,exception);
190 if (status == MagickFalse)
192 difference_image=DestroyImage(difference_image);
193 highlight_image=DestroyImage(highlight_image);
194 return((Image *) NULL);
196 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception);
197 (void) QueryColorCompliance("#f1001ecc",AllCompliance,&highlight,exception);
198 artifact=GetImageArtifact(image,"highlight-color");
199 if (artifact != (const char *) NULL)
200 (void) QueryColorCompliance(artifact,AllCompliance,&highlight,exception);
201 (void) QueryColorCompliance("#ffffffcc",AllCompliance,&lowlight,exception);
202 artifact=GetImageArtifact(image,"lowlight-color");
203 if (artifact != (const char *) NULL)
204 (void) QueryColorCompliance(artifact,AllCompliance,&lowlight,exception);
206 Generate difference image.
209 image_view=AcquireVirtualCacheView(image,exception);
210 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
211 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
212 #if defined(MAGICKCORE_OPENMP_SUPPORT)
213 #pragma omp parallel for schedule(static,4) shared(status) \
214 magick_threads(image,highlight_image,image->rows,1)
216 for (y=0; y < (ssize_t) image->rows; y++)
221 register const Quantum
231 if (status == MagickFalse)
233 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
234 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,image->columns,1,
236 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,highlight_image->columns,
238 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
239 (r == (Quantum *) NULL))
244 for (x=0; x < (ssize_t) image->columns; x++)
256 if (GetPixelReadMask(image,p) == 0)
258 SetPixelInfoPixel(highlight_image,&lowlight,r);
259 p+=GetPixelChannels(image);
260 q+=GetPixelChannels(reconstruct_image);
261 r+=GetPixelChannels(highlight_image);
264 difference=MagickFalse;
265 Sa=QuantumScale*GetPixelAlpha(image,p);
266 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
267 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
272 PixelChannel channel=GetPixelChannelChannel(image,i);
273 PixelTrait traits=GetPixelChannelTraits(image,channel);
274 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
276 if ((traits == UndefinedPixelTrait) ||
277 (reconstruct_traits == UndefinedPixelTrait) ||
278 ((reconstruct_traits & UpdatePixelTrait) == 0))
280 distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
281 if (fabs(distance) >= MagickEpsilon)
282 difference=MagickTrue;
284 if (difference == MagickFalse)
285 SetPixelInfoPixel(highlight_image,&lowlight,r);
287 SetPixelInfoPixel(highlight_image,&highlight,r);
288 p+=GetPixelChannels(image);
289 q+=GetPixelChannels(reconstruct_image);
290 r+=GetPixelChannels(highlight_image);
292 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
293 if (sync == MagickFalse)
296 highlight_view=DestroyCacheView(highlight_view);
297 reconstruct_view=DestroyCacheView(reconstruct_view);
298 image_view=DestroyCacheView(image_view);
299 (void) CompositeImage(difference_image,highlight_image,image->compose,
300 MagickTrue,0,0,exception);
301 highlight_image=DestroyImage(highlight_image);
302 if (status == MagickFalse)
303 difference_image=DestroyImage(difference_image);
304 return(difference_image);
308 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
312 % G e t I m a g e D i s t o r t i o n %
316 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
318 % GetImageDistortion() compares one or more pixel channels of an image to a
319 % reconstructed image and returns the specified distortion metric.
321 % The format of the GetImageDistortion method is:
323 % MagickBooleanType GetImageDistortion(const Image *image,
324 % const Image *reconstruct_image,const MetricType metric,
325 % double *distortion,ExceptionInfo *exception)
327 % A description of each parameter follows:
329 % o image: the image.
331 % o reconstruct_image: the reconstruct image.
333 % o metric: the metric.
335 % o distortion: the computed distortion between the images.
337 % o exception: return any errors or warnings in this structure.
341 static inline double MagickMax(const double x,const double y)
348 static MagickBooleanType GetAbsoluteDistortion(const Image *image,
349 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
365 Compute the absolute difference in pixels between two images.
368 if (image->fuzz == 0.0)
369 fuzz=MagickMax(reconstruct_image->fuzz,MagickSQ1_2)*
370 MagickMax(reconstruct_image->fuzz,MagickSQ1_2);
372 if (reconstruct_image->fuzz == 0.0)
373 fuzz=MagickMax(image->fuzz,MagickSQ1_2)*
374 MagickMax(image->fuzz,MagickSQ1_2);
376 fuzz=MagickMax(image->fuzz,MagickSQ1_2)*
377 MagickMax(reconstruct_image->fuzz,MagickSQ1_2);
378 image_view=AcquireVirtualCacheView(image,exception);
379 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
380 #if defined(MAGICKCORE_OPENMP_SUPPORT)
381 #pragma omp parallel for schedule(static,4) shared(status) \
382 magick_threads(image,image,image->rows,1)
384 for (y=0; y < (ssize_t) image->rows; y++)
387 channel_distortion[MaxPixelChannels+1];
389 register const Quantum
397 if (status == MagickFalse)
399 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
400 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
402 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
407 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
408 for (x=0; x < (ssize_t) image->columns; x++)
420 if (GetPixelReadMask(image,p) == 0)
422 p+=GetPixelChannels(image);
423 q+=GetPixelChannels(reconstruct_image);
426 difference=MagickFalse;
427 Sa=QuantumScale*GetPixelAlpha(image,p);
428 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
429 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
434 PixelChannel channel=GetPixelChannelChannel(image,i);
435 PixelTrait traits=GetPixelChannelTraits(image,channel);
436 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
438 if ((traits == UndefinedPixelTrait) ||
439 (reconstruct_traits == UndefinedPixelTrait) ||
440 ((reconstruct_traits & UpdatePixelTrait) == 0))
442 distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
443 if ((distance*distance) > fuzz)
445 difference=MagickTrue;
449 if (difference != MagickFalse)
451 channel_distortion[i]++;
452 channel_distortion[CompositePixelChannel]++;
454 p+=GetPixelChannels(image);
455 q+=GetPixelChannels(reconstruct_image);
457 #if defined(MAGICKCORE_OPENMP_SUPPORT)
458 #pragma omp critical (MagickCore_GetAbsoluteError)
460 for (i=0; i <= MaxPixelChannels; i++)
461 distortion[i]+=channel_distortion[i];
463 reconstruct_view=DestroyCacheView(reconstruct_view);
464 image_view=DestroyCacheView(image_view);
468 static MagickBooleanType GetFuzzDistortion(const Image *image,
469 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
485 image_view=AcquireVirtualCacheView(image,exception);
486 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
487 #if defined(MAGICKCORE_OPENMP_SUPPORT)
488 #pragma omp parallel for schedule(static,4) shared(status) \
489 magick_threads(image,image,image->rows,1)
491 for (y=0; y < (ssize_t) image->rows; y++)
494 channel_distortion[MaxPixelChannels+1];
496 register const Quantum
504 if (status == MagickFalse)
506 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
507 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
509 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
514 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
515 for (x=0; x < (ssize_t) image->columns; x++)
524 if (GetPixelReadMask(image,p) == 0)
526 p+=GetPixelChannels(image);
527 q+=GetPixelChannels(reconstruct_image);
530 Sa=QuantumScale*GetPixelAlpha(image,p);
531 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
532 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
537 PixelChannel channel=GetPixelChannelChannel(image,i);
538 PixelTrait traits=GetPixelChannelTraits(image,channel);
539 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
541 if ((traits == UndefinedPixelTrait) ||
542 (reconstruct_traits == UndefinedPixelTrait) ||
543 ((reconstruct_traits & UpdatePixelTrait) == 0))
545 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
547 channel_distortion[i]+=distance*distance;
548 channel_distortion[CompositePixelChannel]+=distance*distance;
550 p+=GetPixelChannels(image);
551 q+=GetPixelChannels(reconstruct_image);
553 #if defined(MAGICKCORE_OPENMP_SUPPORT)
554 #pragma omp critical (MagickCore_GetFuzzDistortion)
556 for (i=0; i <= MaxPixelChannels; i++)
557 distortion[i]+=channel_distortion[i];
559 reconstruct_view=DestroyCacheView(reconstruct_view);
560 image_view=DestroyCacheView(image_view);
561 for (i=0; i <= MaxPixelChannels; i++)
562 distortion[i]/=((double) image->columns*image->rows);
563 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
564 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
568 static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
569 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
585 image_view=AcquireVirtualCacheView(image,exception);
586 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
587 #if defined(MAGICKCORE_OPENMP_SUPPORT)
588 #pragma omp parallel for schedule(static,4) shared(status) \
589 magick_threads(image,image,image->rows,1)
591 for (y=0; y < (ssize_t) image->rows; y++)
594 channel_distortion[MaxPixelChannels+1];
596 register const Quantum
604 if (status == MagickFalse)
606 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
607 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
609 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
614 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
615 for (x=0; x < (ssize_t) image->columns; x++)
624 if (GetPixelReadMask(image,p) == 0)
626 p+=GetPixelChannels(image);
627 q+=GetPixelChannels(reconstruct_image);
630 Sa=QuantumScale*GetPixelAlpha(image,p);
631 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
632 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
637 PixelChannel channel=GetPixelChannelChannel(image,i);
638 PixelTrait traits=GetPixelChannelTraits(image,channel);
639 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
641 if ((traits == UndefinedPixelTrait) ||
642 (reconstruct_traits == UndefinedPixelTrait) ||
643 ((reconstruct_traits & UpdatePixelTrait) == 0))
645 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
647 channel_distortion[i]+=distance;
648 channel_distortion[CompositePixelChannel]+=distance;
650 p+=GetPixelChannels(image);
651 q+=GetPixelChannels(reconstruct_image);
653 #if defined(MAGICKCORE_OPENMP_SUPPORT)
654 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
656 for (i=0; i <= MaxPixelChannels; i++)
657 distortion[i]+=channel_distortion[i];
659 reconstruct_view=DestroyCacheView(reconstruct_view);
660 image_view=DestroyCacheView(image_view);
661 for (i=0; i <= MaxPixelChannels; i++)
662 distortion[i]/=((double) image->columns*image->rows);
663 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
667 static MagickBooleanType GetMeanErrorPerPixel(Image *image,
668 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
689 image_view=AcquireVirtualCacheView(image,exception);
690 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
691 for (y=0; y < (ssize_t) image->rows; y++)
693 register const Quantum
700 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
701 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
703 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
708 for (x=0; x < (ssize_t) image->columns; x++)
717 if (GetPixelReadMask(image,p) == 0)
719 p+=GetPixelChannels(image);
720 q+=GetPixelChannels(reconstruct_image);
723 Sa=QuantumScale*GetPixelAlpha(image,p);
724 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
725 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
730 PixelChannel channel=GetPixelChannelChannel(image,i);
731 PixelTrait traits=GetPixelChannelTraits(image,channel);
732 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
734 if ((traits == UndefinedPixelTrait) ||
735 (reconstruct_traits == UndefinedPixelTrait) ||
736 ((reconstruct_traits & UpdatePixelTrait) == 0))
738 distance=fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q));
739 distortion[i]+=distance;
740 distortion[CompositePixelChannel]+=distance;
741 mean_error+=distance*distance;
742 if (distance > maximum_error)
743 maximum_error=distance;
746 p+=GetPixelChannels(image);
747 q+=GetPixelChannels(reconstruct_image);
750 reconstruct_view=DestroyCacheView(reconstruct_view);
751 image_view=DestroyCacheView(image_view);
752 image->error.mean_error_per_pixel=distortion[CompositePixelChannel]/area;
753 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
754 image->error.normalized_maximum_error=QuantumScale*maximum_error;
758 static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
759 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
775 image_view=AcquireVirtualCacheView(image,exception);
776 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
777 #if defined(MAGICKCORE_OPENMP_SUPPORT)
778 #pragma omp parallel for schedule(static,4) shared(status) \
779 magick_threads(image,image,image->rows,1)
781 for (y=0; y < (ssize_t) image->rows; y++)
784 channel_distortion[MaxPixelChannels+1];
786 register const Quantum
794 if (status == MagickFalse)
796 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
797 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
799 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
804 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
805 for (x=0; x < (ssize_t) image->columns; x++)
814 if (GetPixelReadMask(image,p) == 0)
816 p+=GetPixelChannels(image);
817 q+=GetPixelChannels(reconstruct_image);
820 Sa=QuantumScale*GetPixelAlpha(image,p);
821 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
822 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
827 PixelChannel channel=GetPixelChannelChannel(image,i);
828 PixelTrait traits=GetPixelChannelTraits(image,channel);
829 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
831 if ((traits == UndefinedPixelTrait) ||
832 (reconstruct_traits == UndefinedPixelTrait) ||
833 ((reconstruct_traits & UpdatePixelTrait) == 0))
835 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
837 channel_distortion[i]+=distance*distance;
838 channel_distortion[CompositePixelChannel]+=distance*distance;
840 p+=GetPixelChannels(image);
841 q+=GetPixelChannels(reconstruct_image);
843 #if defined(MAGICKCORE_OPENMP_SUPPORT)
844 #pragma omp critical (MagickCore_GetMeanSquaredError)
846 for (i=0; i <= MaxPixelChannels; i++)
847 distortion[i]+=channel_distortion[i];
849 reconstruct_view=DestroyCacheView(reconstruct_view);
850 image_view=DestroyCacheView(image_view);
851 for (i=0; i <= MaxPixelChannels; i++)
852 distortion[i]/=((double) image->columns*image->rows);
853 distortion[CompositePixelChannel]/=GetImageChannels(image);
857 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
858 const Image *image,const Image *reconstruct_image,double *distortion,
859 ExceptionInfo *exception)
861 #define SimilarityImageTag "Similarity/Image"
869 *reconstruct_statistics;
887 Normalize to account for variation due to lighting and exposure condition.
889 image_statistics=GetImageStatistics(image,exception);
890 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
891 if ((image_statistics == (ChannelStatistics *) NULL) ||
892 (reconstruct_statistics == (ChannelStatistics *) NULL))
894 if (image_statistics != (ChannelStatistics *) NULL)
895 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
897 if (reconstruct_statistics != (ChannelStatistics *) NULL)
898 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
899 reconstruct_statistics);
904 for (i=0; i <= MaxPixelChannels; i++)
906 area=1.0/((double) image->columns*image->rows);
907 image_view=AcquireVirtualCacheView(image,exception);
908 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
909 for (y=0; y < (ssize_t) image->rows; y++)
911 register const Quantum
918 if (status == MagickFalse)
920 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
921 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
923 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
928 for (x=0; x < (ssize_t) image->columns; x++)
937 if (GetPixelReadMask(image,p) == 0)
939 p+=GetPixelChannels(image);
940 q+=GetPixelChannels(reconstruct_image);
943 Sa=QuantumScale*GetPixelAlpha(image,p);
944 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
945 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
947 PixelChannel channel=GetPixelChannelChannel(image,i);
948 PixelTrait traits=GetPixelChannelTraits(image,channel);
949 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
951 if ((traits == UndefinedPixelTrait) ||
952 (reconstruct_traits == UndefinedPixelTrait) ||
953 ((reconstruct_traits & UpdatePixelTrait) == 0))
955 distortion[i]+=area*QuantumScale*(Sa*p[i]-image_statistics[i].mean)*
956 (Da*GetPixelChannel(reconstruct_image,channel,q)-
957 reconstruct_statistics[channel].mean);
959 p+=GetPixelChannels(image);
960 q+=GetPixelChannels(reconstruct_image);
962 if (image->progress_monitor != (MagickProgressMonitor) NULL)
967 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
969 if (proceed == MagickFalse)
973 reconstruct_view=DestroyCacheView(reconstruct_view);
974 image_view=DestroyCacheView(image_view);
976 Divide by the standard deviation.
978 distortion[CompositePixelChannel]=0.0;
979 for (i=0; i < MaxPixelChannels; i++)
984 PixelChannel channel=GetPixelChannelChannel(image,i);
985 gamma=image_statistics[i].standard_deviation*
986 reconstruct_statistics[channel].standard_deviation;
987 gamma=PerceptibleReciprocal(gamma);
988 distortion[i]=QuantumRange*gamma*distortion[i];
989 distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
991 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
992 GetImageChannels(image));
996 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
997 reconstruct_statistics);
998 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1003 static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
1004 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1017 image_view=AcquireVirtualCacheView(image,exception);
1018 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1019 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1020 #pragma omp parallel for schedule(static,4) shared(status) \
1021 magick_threads(image,image,image->rows,1)
1023 for (y=0; y < (ssize_t) image->rows; y++)
1026 channel_distortion[MaxPixelChannels+1];
1028 register const Quantum
1036 if (status == MagickFalse)
1038 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1039 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1041 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
1046 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
1047 for (x=0; x < (ssize_t) image->columns; x++)
1056 if (GetPixelReadMask(image,p) == 0)
1058 p+=GetPixelChannels(image);
1059 q+=GetPixelChannels(reconstruct_image);
1062 Sa=QuantumScale*GetPixelAlpha(image,p);
1063 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
1064 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1069 PixelChannel channel=GetPixelChannelChannel(image,i);
1070 PixelTrait traits=GetPixelChannelTraits(image,channel);
1071 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1073 if ((traits == UndefinedPixelTrait) ||
1074 (reconstruct_traits == UndefinedPixelTrait) ||
1075 ((reconstruct_traits & UpdatePixelTrait) == 0))
1077 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
1079 if (distance > channel_distortion[i])
1080 channel_distortion[i]=distance;
1081 if (distance > channel_distortion[CompositePixelChannel])
1082 channel_distortion[CompositePixelChannel]=distance;
1084 p+=GetPixelChannels(image);
1085 q+=GetPixelChannels(reconstruct_image);
1087 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1088 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1090 for (i=0; i <= MaxPixelChannels; i++)
1091 if (channel_distortion[i] > distortion[i])
1092 distortion[i]=channel_distortion[i];
1094 reconstruct_view=DestroyCacheView(reconstruct_view);
1095 image_view=DestroyCacheView(image_view);
1099 static inline double MagickLog10(const double x)
1101 #define Log10Epsilon (1.0e-11)
1103 if (fabs(x) < Log10Epsilon)
1104 return(log10(Log10Epsilon));
1105 return(log10(fabs(x)));
1108 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
1109 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1117 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1118 for (i=0; i <= MaxPixelChannels; i++)
1119 distortion[i]=20.0*MagickLog10((double) 1.0/sqrt(distortion[i]));
1123 static MagickBooleanType GetPerceptualHashDistortion(const Image *image,
1124 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1126 ChannelPerceptualHash
1134 Compute perceptual hash in the sRGB colorspace.
1136 image_phash=GetImagePerceptualHash(image,exception);
1137 if (image_phash == (ChannelPerceptualHash *) NULL)
1138 return(MagickFalse);
1139 reconstruct_phash=GetImagePerceptualHash(reconstruct_image,exception);
1140 if (reconstruct_phash == (ChannelPerceptualHash *) NULL)
1142 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1143 return(MagickFalse);
1145 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1146 #pragma omp parallel for schedule(static,4)
1148 for (channel=0; channel < MaxPixelChannels; channel++)
1157 for (i=0; i < MaximumNumberOfImageMoments; i++)
1163 alpha=image_phash[channel].srgb_hu_phash[i];
1164 beta=reconstruct_phash[channel].srgb_hu_phash[i];
1165 difference+=(beta-alpha)*(beta-alpha);
1167 distortion[channel]+=difference;
1168 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1169 #pragma omp critical (MagickCore_GetPerceptualHashDistortion)
1171 distortion[CompositePixelChannel]+=difference;
1174 Compute perceptual hash in the HCLP colorspace.
1176 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1177 #pragma omp parallel for schedule(static,4)
1179 for (channel=0; channel < MaxPixelChannels; channel++)
1188 for (i=0; i < MaximumNumberOfImageMoments; i++)
1194 alpha=image_phash[channel].hclp_hu_phash[i];
1195 beta=reconstruct_phash[channel].hclp_hu_phash[i];
1196 difference+=(beta-alpha)*(beta-alpha);
1198 distortion[channel]+=difference;
1199 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1200 #pragma omp critical (MagickCore_GetPerceptualHashDistortion)
1202 distortion[CompositePixelChannel]+=difference;
1207 reconstruct_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
1209 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1213 static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
1214 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1222 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1223 for (i=0; i <= MaxPixelChannels; i++)
1224 distortion[i]=sqrt(distortion[i]);
1228 MagickExport MagickBooleanType GetImageDistortion(Image *image,
1229 const Image *reconstruct_image,const MetricType metric,double *distortion,
1230 ExceptionInfo *exception)
1233 *channel_distortion;
1241 assert(image != (Image *) NULL);
1242 assert(image->signature == MagickSignature);
1243 if (image->debug != MagickFalse)
1244 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1245 assert(reconstruct_image != (const Image *) NULL);
1246 assert(reconstruct_image->signature == MagickSignature);
1247 assert(distortion != (double *) NULL);
1249 if (image->debug != MagickFalse)
1250 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1251 if (metric != PerceptualHashErrorMetric)
1252 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1253 ThrowBinaryException(ImageError,"ImageMorphologyDiffers",image->filename);
1255 Get image distortion.
1257 length=MaxPixelChannels+1;
1258 channel_distortion=(double *) AcquireQuantumMemory(length,
1259 sizeof(*channel_distortion));
1260 if (channel_distortion == (double *) NULL)
1261 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1262 (void) ResetMagickMemory(channel_distortion,0,length*
1263 sizeof(*channel_distortion));
1266 case AbsoluteErrorMetric:
1268 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1272 case FuzzErrorMetric:
1274 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1278 case MeanAbsoluteErrorMetric:
1280 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1281 channel_distortion,exception);
1284 case MeanErrorPerPixelErrorMetric:
1286 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1290 case MeanSquaredErrorMetric:
1292 status=GetMeanSquaredDistortion(image,reconstruct_image,
1293 channel_distortion,exception);
1296 case NormalizedCrossCorrelationErrorMetric:
1299 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1300 channel_distortion,exception);
1303 case PeakAbsoluteErrorMetric:
1305 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1306 channel_distortion,exception);
1309 case PeakSignalToNoiseRatioErrorMetric:
1311 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1312 channel_distortion,exception);
1315 case PerceptualHashErrorMetric:
1317 status=GetPerceptualHashDistortion(image,reconstruct_image,
1318 channel_distortion,exception);
1321 case RootMeanSquaredErrorMetric:
1323 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1324 channel_distortion,exception);
1328 *distortion=channel_distortion[CompositePixelChannel];
1329 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1330 (void) FormatImageProperty(image,"distortion","%.*g",GetMagickPrecision(),
1336 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1340 % G e t I m a g e D i s t o r t i o n s %
1344 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1346 % GetImageDistortions() compares the pixel channels of an image to a
1347 % reconstructed image and returns the specified distortion metric for each
1350 % The format of the GetImageDistortions method is:
1352 % double *GetImageDistortions(const Image *image,
1353 % const Image *reconstruct_image,const MetricType metric,
1354 % ExceptionInfo *exception)
1356 % A description of each parameter follows:
1358 % o image: the image.
1360 % o reconstruct_image: the reconstruct image.
1362 % o metric: the metric.
1364 % o exception: return any errors or warnings in this structure.
1367 MagickExport double *GetImageDistortions(Image *image,
1368 const Image *reconstruct_image,const MetricType metric,
1369 ExceptionInfo *exception)
1372 *channel_distortion;
1380 assert(image != (Image *) NULL);
1381 assert(image->signature == MagickSignature);
1382 if (image->debug != MagickFalse)
1383 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1384 assert(reconstruct_image != (const Image *) NULL);
1385 assert(reconstruct_image->signature == MagickSignature);
1386 if (image->debug != MagickFalse)
1387 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1388 if (metric != PerceptualHashErrorMetric)
1389 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1391 (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
1392 "ImageMorphologyDiffers","`%s'",image->filename);
1393 return((double *) NULL);
1396 Get image distortion.
1398 length=MaxPixelChannels+1UL;
1399 channel_distortion=(double *) AcquireQuantumMemory(length,
1400 sizeof(*channel_distortion));
1401 if (channel_distortion == (double *) NULL)
1402 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1403 (void) ResetMagickMemory(channel_distortion,0,length*
1404 sizeof(*channel_distortion));
1408 case AbsoluteErrorMetric:
1410 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1414 case FuzzErrorMetric:
1416 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1420 case MeanAbsoluteErrorMetric:
1422 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1423 channel_distortion,exception);
1426 case MeanErrorPerPixelErrorMetric:
1428 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1432 case MeanSquaredErrorMetric:
1434 status=GetMeanSquaredDistortion(image,reconstruct_image,
1435 channel_distortion,exception);
1438 case NormalizedCrossCorrelationErrorMetric:
1441 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1442 channel_distortion,exception);
1445 case PeakAbsoluteErrorMetric:
1447 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1448 channel_distortion,exception);
1451 case PeakSignalToNoiseRatioErrorMetric:
1453 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1454 channel_distortion,exception);
1457 case PerceptualHashErrorMetric:
1459 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1460 channel_distortion,exception);
1463 case RootMeanSquaredErrorMetric:
1465 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1466 channel_distortion,exception);
1470 if (status == MagickFalse)
1472 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1473 return((double *) NULL);
1475 return(channel_distortion);
1479 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1483 % I s I m a g e s E q u a l %
1487 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1489 % IsImagesEqual() measures the difference between colors at each pixel
1490 % location of two images. A value other than 0 means the colors match
1491 % exactly. Otherwise an error measure is computed by summing over all
1492 % pixels in an image the distance squared in RGB space between each image
1493 % pixel and its corresponding pixel in the reconstruct image. The error
1494 % measure is assigned to these image members:
1496 % o mean_error_per_pixel: The mean error for any single pixel in
1499 % o normalized_mean_error: The normalized mean quantization error for
1500 % any single pixel in the image. This distance measure is normalized to
1501 % a range between 0 and 1. It is independent of the range of red, green,
1502 % and blue values in the image.
1504 % o normalized_maximum_error: The normalized maximum quantization
1505 % error for any single pixel in the image. This distance measure is
1506 % normalized to a range between 0 and 1. It is independent of the range
1507 % of red, green, and blue values in your image.
1509 % A small normalized mean square error, accessed as
1510 % image->normalized_mean_error, suggests the images are very similar in
1511 % spatial layout and color.
1513 % The format of the IsImagesEqual method is:
1515 % MagickBooleanType IsImagesEqual(Image *image,
1516 % const Image *reconstruct_image,ExceptionInfo *exception)
1518 % A description of each parameter follows.
1520 % o image: the image.
1522 % o reconstruct_image: the reconstruct image.
1524 % o exception: return any errors or warnings in this structure.
1527 MagickExport MagickBooleanType IsImagesEqual(Image *image,
1528 const Image *reconstruct_image,ExceptionInfo *exception)
1541 mean_error_per_pixel;
1546 assert(image != (Image *) NULL);
1547 assert(image->signature == MagickSignature);
1548 assert(reconstruct_image != (const Image *) NULL);
1549 assert(reconstruct_image->signature == MagickSignature);
1550 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1551 ThrowBinaryException(ImageError,"ImageMorphologyDiffers",image->filename);
1554 mean_error_per_pixel=0.0;
1556 image_view=AcquireVirtualCacheView(image,exception);
1557 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1558 for (y=0; y < (ssize_t) image->rows; y++)
1560 register const Quantum
1567 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1568 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1570 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1572 for (x=0; x < (ssize_t) image->columns; x++)
1577 if (GetPixelReadMask(image,p) == 0)
1579 p+=GetPixelChannels(image);
1580 q+=GetPixelChannels(reconstruct_image);
1583 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1588 PixelChannel channel=GetPixelChannelChannel(image,i);
1589 PixelTrait traits=GetPixelChannelTraits(image,channel);
1590 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1592 if ((traits == UndefinedPixelTrait) ||
1593 (reconstruct_traits == UndefinedPixelTrait) ||
1594 ((reconstruct_traits & UpdatePixelTrait) == 0))
1596 distance=fabs(p[i]-(double) GetPixelChannel(reconstruct_image,
1598 if (distance >= MagickEpsilon)
1600 mean_error_per_pixel+=distance;
1601 mean_error+=distance*distance;
1602 if (distance > maximum_error)
1603 maximum_error=distance;
1607 p+=GetPixelChannels(image);
1608 q+=GetPixelChannels(reconstruct_image);
1611 reconstruct_view=DestroyCacheView(reconstruct_view);
1612 image_view=DestroyCacheView(image_view);
1613 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1614 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1616 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1617 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1622 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1626 % S i m i l a r i t y I m a g e %
1630 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1632 % SimilarityImage() compares the reference image of the image and returns the
1633 % best match offset. In addition, it returns a similarity image such that an
1634 % exact match location is completely white and if none of the pixels match,
1635 % black, otherwise some gray level in-between.
1637 % The format of the SimilarityImageImage method is:
1639 % Image *SimilarityImage(const Image *image,const Image *reference,
1640 % const MetricType metric,const double similarity_threshold,
1641 % RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
1643 % A description of each parameter follows:
1645 % o image: the image.
1647 % o reference: find an area of the image that closely resembles this image.
1649 % o metric: the metric.
1651 % o similarity_threshold: minimum distortion for (sub)image match.
1653 % o offset: the best match offset of the reference image within the image.
1655 % o similarity: the computed similarity between the images.
1657 % o exception: return any errors or warnings in this structure.
1661 static double GetSimilarityMetric(const Image *image,const Image *reference,
1662 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
1663 ExceptionInfo *exception)
1677 SetGeometry(reference,&geometry);
1678 geometry.x=x_offset;
1679 geometry.y=y_offset;
1680 similarity_image=CropImage(image,&geometry,exception);
1681 if (similarity_image == (Image *) NULL)
1684 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
1686 similarity_image=DestroyImage(similarity_image);
1687 if (status == MagickFalse)
1692 static inline double MagickMin(const double x,const double y)
1699 MagickExport Image *SimilarityImage(Image *image,const Image *reference,
1700 const MetricType metric,const double similarity_threshold,
1701 RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
1703 #define SimilarityImageTag "Similarity/Image"
1720 assert(image != (const Image *) NULL);
1721 assert(image->signature == MagickSignature);
1722 if (image->debug != MagickFalse)
1723 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1724 assert(exception != (ExceptionInfo *) NULL);
1725 assert(exception->signature == MagickSignature);
1726 assert(offset != (RectangleInfo *) NULL);
1727 SetGeometry(reference,offset);
1728 *similarity_metric=MagickMaximumValue;
1729 if (ValidateImageMorphology(image,reference) == MagickFalse)
1730 ThrowImageException(ImageError,"ImageMorphologyDiffers");
1731 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1732 image->rows-reference->rows+1,MagickTrue,exception);
1733 if (similarity_image == (Image *) NULL)
1734 return((Image *) NULL);
1735 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1736 if (status == MagickFalse)
1738 similarity_image=DestroyImage(similarity_image);
1739 return((Image *) NULL);
1741 (void) SetImageAlphaChannel(similarity_image,DeactivateAlphaChannel,
1744 Measure similarity of reference image against image.
1748 similarity_view=AcquireAuthenticCacheView(similarity_image,exception);
1749 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1750 #pragma omp parallel for schedule(static,4) \
1751 shared(progress,status,similarity_metric) \
1752 magick_threads(image,image,image->rows,1)
1754 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
1765 if (status == MagickFalse)
1767 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1768 #pragma omp flush(similarity_metric)
1770 if (*similarity_metric <= similarity_threshold)
1772 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1774 if (q == (Quantum *) NULL)
1779 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
1784 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1785 #pragma omp flush(similarity_metric)
1787 if (*similarity_metric <= similarity_threshold)
1789 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
1790 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1791 #pragma omp critical (MagickCore_SimilarityImage)
1793 if (similarity < *similarity_metric)
1797 *similarity_metric=similarity;
1799 if (metric == PerceptualHashErrorMetric)
1800 similarity=MagickMin(0.01*similarity,1.0);
1801 if (GetPixelReadMask(similarity_image,q) == 0)
1803 SetPixelBackgoundColor(similarity_image,q);
1804 q+=GetPixelChannels(similarity_image);
1807 for (i=0; i < (ssize_t) GetPixelChannels(similarity_image); i++)
1809 PixelChannel channel=GetPixelChannelChannel(image,i);
1810 PixelTrait traits=GetPixelChannelTraits(image,channel);
1811 PixelTrait similarity_traits=GetPixelChannelTraits(similarity_image,
1813 if ((traits == UndefinedPixelTrait) ||
1814 (similarity_traits == UndefinedPixelTrait) ||
1815 ((similarity_traits & UpdatePixelTrait) == 0))
1817 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1818 QuantumRange*similarity),q);
1820 q+=GetPixelChannels(similarity_image);
1822 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
1824 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1829 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1830 #pragma omp critical (MagickCore_SimilarityImage)
1832 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1834 if (proceed == MagickFalse)
1838 similarity_view=DestroyCacheView(similarity_view);
1839 if (status == MagickFalse)
1840 similarity_image=DestroyImage(similarity_image);
1841 return(similarity_image);