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-2010 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 "magick/studio.h"
44 #include "magick/artifact.h"
45 #include "magick/cache-view.h"
46 #include "magick/client.h"
47 #include "magick/color.h"
48 #include "magick/color-private.h"
49 #include "magick/colorspace.h"
50 #include "magick/colorspace-private.h"
51 #include "magick/compare.h"
52 #include "magick/composite-private.h"
53 #include "magick/constitute.h"
54 #include "magick/exception-private.h"
55 #include "magick/geometry.h"
56 #include "magick/image-private.h"
57 #include "magick/list.h"
58 #include "magick/log.h"
59 #include "magick/memory_.h"
60 #include "magick/monitor.h"
61 #include "magick/monitor-private.h"
62 #include "magick/option.h"
63 #include "magick/pixel-private.h"
64 #include "magick/resource_.h"
65 #include "magick/string_.h"
66 #include "magick/statistic.h"
67 #include "magick/transform.h"
68 #include "magick/utility.h"
69 #include "magick/version.h"
72 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
76 % C o m p a r e I m a g e C h a n n e l s %
80 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
82 % CompareImageChannels() compares one or more image channels of an image
83 % to a reconstructed image and returns the difference image.
85 % The format of the CompareImageChannels method is:
87 % Image *CompareImageChannels(const Image *image,
88 % const Image *reconstruct_image,const ChannelType channel,
89 % const MetricType metric,double *distortion,ExceptionInfo *exception)
91 % A description of each parameter follows:
95 % o reconstruct_image: the reconstruct image.
97 % o channel: the channel.
99 % o metric: the metric.
101 % o distortion: the computed distortion between the images.
103 % o exception: return any errors or warnings in this structure.
107 MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
108 const MetricType metric,double *distortion,ExceptionInfo *exception)
113 highlight_image=CompareImageChannels(image,reconstruct_image,AllChannels,
114 metric,distortion,exception);
115 return(highlight_image);
118 MagickExport Image *CompareImageChannels(Image *image,
119 const Image *reconstruct_image,const ChannelType channel,
120 const MetricType metric,double *distortion,ExceptionInfo *exception)
145 assert(image != (Image *) NULL);
146 assert(image->signature == MagickSignature);
147 if (image->debug != MagickFalse)
148 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
149 assert(reconstruct_image != (const Image *) NULL);
150 assert(reconstruct_image->signature == MagickSignature);
151 assert(distortion != (double *) NULL);
153 if (image->debug != MagickFalse)
154 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
155 if ((reconstruct_image->columns != image->columns) ||
156 (reconstruct_image->rows != image->rows))
157 ThrowImageException(ImageError,"ImageSizeDiffers");
158 status=GetImageChannelDistortion(image,reconstruct_image,channel,metric,
159 distortion,exception);
160 if (status == MagickFalse)
161 return((Image *) NULL);
162 difference_image=CloneImage(image,0,0,MagickTrue,exception);
163 if (difference_image == (Image *) NULL)
164 return((Image *) NULL);
165 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel);
166 highlight_image=CloneImage(image,image->columns,image->rows,MagickTrue,
168 if (highlight_image == (Image *) NULL)
170 difference_image=DestroyImage(difference_image);
171 return((Image *) NULL);
173 if (SetImageStorageClass(highlight_image,DirectClass) == MagickFalse)
175 InheritException(exception,&highlight_image->exception);
176 difference_image=DestroyImage(difference_image);
177 highlight_image=DestroyImage(highlight_image);
178 return((Image *) NULL);
180 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel);
181 (void) QueryMagickColor("#f1001ecc",&highlight,exception);
182 artifact=GetImageArtifact(image,"highlight-color");
183 if (artifact != (const char *) NULL)
184 (void) QueryMagickColor(artifact,&highlight,exception);
185 (void) QueryMagickColor("#ffffffcc",&lowlight,exception);
186 artifact=GetImageArtifact(image,"lowlight-color");
187 if (artifact != (const char *) NULL)
188 (void) QueryMagickColor(artifact,&lowlight,exception);
189 if (highlight_image->colorspace == CMYKColorspace)
191 ConvertRGBToCMYK(&highlight);
192 ConvertRGBToCMYK(&lowlight);
195 Generate difference image.
198 GetMagickPixelPacket(image,&zero);
199 image_view=AcquireCacheView(image);
200 reconstruct_view=AcquireCacheView(reconstruct_image);
201 highlight_view=AcquireCacheView(highlight_image);
202 #if defined(MAGICKCORE_OPENMP_SUPPORT)
203 #pragma omp parallel for schedule(dynamic,4) shared(status)
205 for (y=0; y < (ssize_t) image->rows; y++)
214 register const IndexPacket
216 *restrict reconstruct_indexes;
218 register const PixelPacket
223 *restrict highlight_indexes;
231 if (status == MagickFalse)
233 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
234 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
236 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,highlight_image->columns,
238 if ((p == (const PixelPacket *) NULL) ||
239 (q == (const PixelPacket *) NULL) || (r == (PixelPacket *) NULL))
244 indexes=GetCacheViewVirtualIndexQueue(image_view);
245 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
246 highlight_indexes=GetCacheViewAuthenticIndexQueue(highlight_view);
248 reconstruct_pixel=zero;
249 for (x=0; x < (ssize_t) image->columns; x++)
254 SetMagickPixelPacket(image,p,indexes+x,&pixel);
255 SetMagickPixelPacket(reconstruct_image,q,reconstruct_indexes+x,
257 difference=MagickFalse;
258 if (channel == AllChannels)
260 if (IsMagickColorSimilar(&pixel,&reconstruct_pixel) == MagickFalse)
261 difference=MagickTrue;
265 if (((channel & RedChannel) != 0) && (p->red != q->red))
266 difference=MagickTrue;
267 if (((channel & GreenChannel) != 0) && (p->green != q->green))
268 difference=MagickTrue;
269 if (((channel & BlueChannel) != 0) && (p->blue != q->blue))
270 difference=MagickTrue;
271 if (((channel & OpacityChannel) != 0) &&
272 (image->matte != MagickFalse) && (p->opacity != q->opacity))
273 difference=MagickTrue;
274 if ((((channel & IndexChannel) != 0) &&
275 (image->colorspace == CMYKColorspace) &&
276 (reconstruct_image->colorspace == CMYKColorspace)) &&
277 (indexes[x] != reconstruct_indexes[x]))
278 difference=MagickTrue;
280 if (difference != MagickFalse)
281 SetPixelPacket(highlight_image,&highlight,r,highlight_indexes+x);
283 SetPixelPacket(highlight_image,&lowlight,r,highlight_indexes+x);
288 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
289 if (sync == MagickFalse)
292 highlight_view=DestroyCacheView(highlight_view);
293 reconstruct_view=DestroyCacheView(reconstruct_view);
294 image_view=DestroyCacheView(image_view);
295 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0);
296 highlight_image=DestroyImage(highlight_image);
297 if (status == MagickFalse)
298 difference_image=DestroyImage(difference_image);
299 return(difference_image);
303 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
307 % G e t I m a g e C h a n n e l D i s t o r t i o n %
311 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
313 % GetImageChannelDistortion() compares one or more image channels of an image
314 % to a reconstructed image and returns the specified distortion metric.
316 % The format of the CompareImageChannels method is:
318 % MagickBooleanType GetImageChannelDistortion(const Image *image,
319 % const Image *reconstruct_image,const ChannelType channel,
320 % const MetricType metric,double *distortion,ExceptionInfo *exception)
322 % A description of each parameter follows:
324 % o image: the image.
326 % o reconstruct_image: the reconstruct image.
328 % o channel: the channel.
330 % o metric: the metric.
332 % o distortion: the computed distortion between the images.
334 % o exception: return any errors or warnings in this structure.
338 MagickExport MagickBooleanType GetImageDistortion(Image *image,
339 const Image *reconstruct_image,const MetricType metric,double *distortion,
340 ExceptionInfo *exception)
345 status=GetImageChannelDistortion(image,reconstruct_image,AllChannels,
346 metric,distortion,exception);
350 static MagickBooleanType GetAbsoluteDistortion(const Image *image,
351 const Image *reconstruct_image,const ChannelType channel,double *distortion,
352 ExceptionInfo *exception)
368 Compute the absolute difference in pixels between two images.
371 GetMagickPixelPacket(image,&zero);
372 image_view=AcquireCacheView(image);
373 reconstruct_view=AcquireCacheView(reconstruct_image);
374 #if defined(MAGICKCORE_OPENMP_SUPPORT)
375 #pragma omp parallel for schedule(dynamic,4) shared(status)
377 for (y=0; y < (ssize_t) image->rows; y++)
380 channel_distortion[AllChannels+1];
386 register const IndexPacket
388 *restrict reconstruct_indexes;
390 register const PixelPacket
398 if (status == MagickFalse)
400 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
401 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
403 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
408 indexes=GetCacheViewVirtualIndexQueue(image_view);
409 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
411 reconstruct_pixel=pixel;
412 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
413 for (x=0; x < (ssize_t) image->columns; x++)
415 SetMagickPixelPacket(image,p,indexes+x,&pixel);
416 SetMagickPixelPacket(reconstruct_image,q,reconstruct_indexes+x,
418 if (IsMagickColorSimilar(&pixel,&reconstruct_pixel) == MagickFalse)
420 if ((channel & RedChannel) != 0)
421 channel_distortion[RedChannel]++;
422 if ((channel & GreenChannel) != 0)
423 channel_distortion[GreenChannel]++;
424 if ((channel & BlueChannel) != 0)
425 channel_distortion[BlueChannel]++;
426 if (((channel & OpacityChannel) != 0) &&
427 (image->matte != MagickFalse))
428 channel_distortion[OpacityChannel]++;
429 if (((channel & IndexChannel) != 0) &&
430 (image->colorspace == CMYKColorspace))
431 channel_distortion[BlackChannel]++;
432 channel_distortion[AllChannels]++;
437 #if defined(MAGICKCORE_OPENMP_SUPPORT)
438 #pragma omp critical (MagickCore_GetAbsoluteError)
440 for (i=0; i <= (ssize_t) AllChannels; i++)
441 distortion[i]+=channel_distortion[i];
443 reconstruct_view=DestroyCacheView(reconstruct_view);
444 image_view=DestroyCacheView(image_view);
448 static size_t GetNumberChannels(const Image *image,
449 const ChannelType channel)
455 if ((channel & RedChannel) != 0)
457 if ((channel & GreenChannel) != 0)
459 if ((channel & BlueChannel) != 0)
461 if (((channel & OpacityChannel) != 0) &&
462 (image->matte != MagickFalse))
464 if (((channel & IndexChannel) != 0) &&
465 (image->colorspace == CMYKColorspace))
470 static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
471 const Image *reconstruct_image,const ChannelType channel,
472 double *distortion,ExceptionInfo *exception)
488 image_view=AcquireCacheView(image);
489 reconstruct_view=AcquireCacheView(reconstruct_image);
490 #if defined(MAGICKCORE_OPENMP_SUPPORT)
491 #pragma omp parallel for schedule(dynamic,4) shared(status)
493 for (y=0; y < (ssize_t) image->rows; y++)
496 channel_distortion[AllChannels+1];
498 register const IndexPacket
500 *restrict reconstruct_indexes;
502 register const PixelPacket
510 if (status == MagickFalse)
512 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
513 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,
514 reconstruct_image->columns,1,exception);
515 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
520 indexes=GetCacheViewVirtualIndexQueue(image_view);
521 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
522 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
523 for (x=0; x < (ssize_t) image->columns; x++)
528 if ((channel & RedChannel) != 0)
530 distance=QuantumScale*fabs(p->red-(double) q->red);
531 channel_distortion[RedChannel]+=distance;
532 channel_distortion[AllChannels]+=distance;
534 if ((channel & GreenChannel) != 0)
536 distance=QuantumScale*fabs(p->green-(double) q->green);
537 channel_distortion[GreenChannel]+=distance;
538 channel_distortion[AllChannels]+=distance;
540 if ((channel & BlueChannel) != 0)
542 distance=QuantumScale*fabs(p->blue-(double) q->blue);
543 channel_distortion[BlueChannel]+=distance;
544 channel_distortion[AllChannels]+=distance;
546 if (((channel & OpacityChannel) != 0) &&
547 (image->matte != MagickFalse))
549 distance=QuantumScale*fabs(p->opacity-(double) q->opacity);
550 channel_distortion[OpacityChannel]+=distance;
551 channel_distortion[AllChannels]+=distance;
553 if (((channel & IndexChannel) != 0) &&
554 (image->colorspace == CMYKColorspace))
556 distance=QuantumScale*fabs(indexes[x]-(double)
557 reconstruct_indexes[x]);
558 channel_distortion[BlackChannel]+=distance;
559 channel_distortion[AllChannels]+=distance;
564 #if defined(MAGICKCORE_OPENMP_SUPPORT)
565 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
567 for (i=0; i <= (ssize_t) AllChannels; i++)
568 distortion[i]+=channel_distortion[i];
570 reconstruct_view=DestroyCacheView(reconstruct_view);
571 image_view=DestroyCacheView(image_view);
572 for (i=0; i <= (ssize_t) AllChannels; i++)
573 distortion[i]/=((double) image->columns*image->rows);
574 distortion[AllChannels]/=(double) GetNumberChannels(image,channel);
578 static MagickBooleanType GetMeanErrorPerPixel(Image *image,
579 const Image *reconstruct_image,const ChannelType channel,double *distortion,
580 ExceptionInfo *exception)
605 image_view=AcquireCacheView(image);
606 reconstruct_view=AcquireCacheView(reconstruct_image);
607 for (y=0; y < (ssize_t) image->rows; y++)
609 register const IndexPacket
611 *restrict reconstruct_indexes;
613 register const PixelPacket
620 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
621 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
623 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
628 indexes=GetCacheViewVirtualIndexQueue(image_view);
629 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
630 for (x=0; x < (ssize_t) image->columns; x++)
635 if ((channel & OpacityChannel) != 0)
637 if (image->matte != MagickFalse)
638 alpha=(MagickRealType) (QuantumScale*(GetAlphaPixelComponent(p)));
639 if (reconstruct_image->matte != MagickFalse)
640 beta=(MagickRealType) (QuantumScale*GetAlphaPixelComponent(q));
642 if ((channel & RedChannel) != 0)
644 distance=fabs(alpha*p->red-beta*q->red);
645 distortion[RedChannel]+=distance;
646 distortion[AllChannels]+=distance;
647 mean_error+=distance*distance;
648 if (distance > maximum_error)
649 maximum_error=distance;
652 if ((channel & GreenChannel) != 0)
654 distance=fabs(alpha*p->green-beta*q->green);
655 distortion[GreenChannel]+=distance;
656 distortion[AllChannels]+=distance;
657 mean_error+=distance*distance;
658 if (distance > maximum_error)
659 maximum_error=distance;
662 if ((channel & BlueChannel) != 0)
664 distance=fabs(alpha*p->blue-beta*q->blue);
665 distortion[BlueChannel]+=distance;
666 distortion[AllChannels]+=distance;
667 mean_error+=distance*distance;
668 if (distance > maximum_error)
669 maximum_error=distance;
672 if (((channel & OpacityChannel) != 0) &&
673 (image->matte != MagickFalse))
675 distance=fabs((double) p->opacity-q->opacity);
676 distortion[OpacityChannel]+=distance;
677 distortion[AllChannels]+=distance;
678 mean_error+=distance*distance;
679 if (distance > maximum_error)
680 maximum_error=distance;
683 if (((channel & IndexChannel) != 0) &&
684 (image->colorspace == CMYKColorspace) &&
685 (reconstruct_image->colorspace == CMYKColorspace))
687 distance=fabs(alpha*indexes[x]-beta*reconstruct_indexes[x]);
688 distortion[BlackChannel]+=distance;
689 distortion[AllChannels]+=distance;
690 mean_error+=distance*distance;
691 if (distance > maximum_error)
692 maximum_error=distance;
699 reconstruct_view=DestroyCacheView(reconstruct_view);
700 image_view=DestroyCacheView(image_view);
701 image->error.mean_error_per_pixel=distortion[AllChannels]/area;
702 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
703 image->error.normalized_maximum_error=QuantumScale*maximum_error;
707 static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
708 const Image *reconstruct_image,const ChannelType channel,
709 double *distortion,ExceptionInfo *exception)
725 image_view=AcquireCacheView(image);
726 reconstruct_view=AcquireCacheView(reconstruct_image);
727 #if defined(MAGICKCORE_OPENMP_SUPPORT)
728 #pragma omp parallel for schedule(dynamic,4) shared(status)
730 for (y=0; y < (ssize_t) image->rows; y++)
733 channel_distortion[AllChannels+1];
735 register const IndexPacket
737 *restrict reconstruct_indexes;
739 register const PixelPacket
747 if (status == MagickFalse)
749 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
750 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,
751 reconstruct_image->columns,1,exception);
752 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
757 indexes=GetCacheViewVirtualIndexQueue(image_view);
758 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
759 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
760 for (x=0; x < (ssize_t) image->columns; x++)
765 if ((channel & RedChannel) != 0)
767 distance=QuantumScale*(p->red-(MagickRealType) q->red);
768 channel_distortion[RedChannel]+=distance*distance;
769 channel_distortion[AllChannels]+=distance*distance;
771 if ((channel & GreenChannel) != 0)
773 distance=QuantumScale*(p->green-(MagickRealType) q->green);
774 channel_distortion[GreenChannel]+=distance*distance;
775 channel_distortion[AllChannels]+=distance*distance;
777 if ((channel & BlueChannel) != 0)
779 distance=QuantumScale*(p->blue-(MagickRealType) q->blue);
780 channel_distortion[BlueChannel]+=distance*distance;
781 channel_distortion[AllChannels]+=distance*distance;
783 if (((channel & OpacityChannel) != 0) &&
784 (image->matte != MagickFalse))
786 distance=QuantumScale*(p->opacity-(MagickRealType) q->opacity);
787 channel_distortion[OpacityChannel]+=distance*distance;
788 channel_distortion[AllChannels]+=distance*distance;
790 if (((channel & IndexChannel) != 0) &&
791 (image->colorspace == CMYKColorspace) &&
792 (reconstruct_image->colorspace == CMYKColorspace))
794 distance=QuantumScale*(indexes[x]-(MagickRealType)
795 reconstruct_indexes[x]);
796 channel_distortion[BlackChannel]+=distance*distance;
797 channel_distortion[AllChannels]+=distance*distance;
802 #if defined(MAGICKCORE_OPENMP_SUPPORT)
803 #pragma omp critical (MagickCore_GetMeanSquaredError)
805 for (i=0; i <= (ssize_t) AllChannels; i++)
806 distortion[i]+=channel_distortion[i];
808 reconstruct_view=DestroyCacheView(reconstruct_view);
809 image_view=DestroyCacheView(image_view);
810 for (i=0; i <= (ssize_t) AllChannels; i++)
811 distortion[i]/=((double) image->columns*image->rows);
812 distortion[AllChannels]/=(double) GetNumberChannels(image,channel);
816 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
817 const Image *image,const Image *reconstruct_image,const ChannelType channel,
818 double *distortion,ExceptionInfo *exception)
820 #define SimilarityImageTag "Similarity/Image"
828 *reconstruct_statistics;
848 image_statistics=GetImageChannelStatistics(image,exception);
849 reconstruct_statistics=GetImageChannelStatistics(reconstruct_image,exception);
852 for (i=0; i <= (ssize_t) AllChannels; i++)
854 area=1.0/((MagickRealType) image->columns*image->rows);
855 image_view=AcquireCacheView(image);
856 reconstruct_view=AcquireCacheView(reconstruct_image);
857 for (y=0; y < (ssize_t) image->rows; y++)
859 register const IndexPacket
861 *restrict reconstruct_indexes;
863 register const PixelPacket
870 if (status == MagickFalse)
872 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
873 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
875 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
880 indexes=GetCacheViewVirtualIndexQueue(image_view);
881 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
882 for (x=0; x < (ssize_t) image->columns; x++)
884 if ((channel & RedChannel) != 0)
885 distortion[RedChannel]+=area*QuantumScale*(p->red-
886 image_statistics[RedChannel].mean)*(q->red-
887 reconstruct_statistics[RedChannel].mean);
888 if ((channel & GreenChannel) != 0)
889 distortion[GreenChannel]+=area*QuantumScale*(p->green-
890 image_statistics[GreenChannel].mean)*(q->green-
891 reconstruct_statistics[GreenChannel].mean);
892 if ((channel & BlueChannel) != 0)
893 distortion[BlueChannel]+=area*QuantumScale*(p->blue-
894 image_statistics[BlueChannel].mean)*(q->blue-
895 reconstruct_statistics[BlueChannel].mean);
896 if (((channel & OpacityChannel) != 0) &&
897 (image->matte != MagickFalse))
898 distortion[OpacityChannel]+=area*QuantumScale*(p->opacity-
899 image_statistics[OpacityChannel].mean)*(q->opacity-
900 reconstruct_statistics[OpacityChannel].mean);
901 if (((channel & IndexChannel) != 0) &&
902 (image->colorspace == CMYKColorspace) &&
903 (reconstruct_image->colorspace == CMYKColorspace))
904 distortion[BlackChannel]+=area*QuantumScale*(indexes[x]-
905 image_statistics[OpacityChannel].mean)*(reconstruct_indexes[x]-
906 reconstruct_statistics[OpacityChannel].mean);
910 if (image->progress_monitor != (MagickProgressMonitor) NULL)
915 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
917 if (proceed == MagickFalse)
921 reconstruct_view=DestroyCacheView(reconstruct_view);
922 image_view=DestroyCacheView(image_view);
924 Divide by the standard deviation.
926 for (i=0; i < (ssize_t) AllChannels; i++)
931 alpha=image_statistics[i].standard_deviation*
932 reconstruct_statistics[i].standard_deviation;
933 if (fabs(alpha) <= MagickEpsilon)
938 distortion[i]=QuantumRange*distortion[i]/alpha;
940 distortion[AllChannels]=0.0;
941 if ((channel & RedChannel) != 0)
942 distortion[AllChannels]+=distortion[RedChannel]*distortion[RedChannel];
943 if ((channel & GreenChannel) != 0)
944 distortion[AllChannels]+=distortion[GreenChannel]*distortion[GreenChannel];
945 if ((channel & BlueChannel) != 0)
946 distortion[AllChannels]+=distortion[BlueChannel]*distortion[BlueChannel];
947 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
948 distortion[AllChannels]+=distortion[OpacityChannel]*
949 distortion[OpacityChannel];
950 if (((channel & IndexChannel) != 0) &&
951 (image->colorspace == CMYKColorspace))
952 distortion[AllChannels]+=distortion[BlackChannel]*distortion[BlackChannel];
953 distortion[AllChannels]=sqrt(distortion[AllChannels]/GetNumberChannels(image,
958 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
959 reconstruct_statistics);
960 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
965 static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
966 const Image *reconstruct_image,const ChannelType channel,
967 double *distortion,ExceptionInfo *exception)
980 image_view=AcquireCacheView(image);
981 reconstruct_view=AcquireCacheView(reconstruct_image);
982 #if defined(MAGICKCORE_OPENMP_SUPPORT)
983 #pragma omp parallel for schedule(dynamic,4) shared(status)
985 for (y=0; y < (ssize_t) image->rows; y++)
988 channel_distortion[AllChannels+1];
990 register const IndexPacket
992 *restrict reconstruct_indexes;
994 register const PixelPacket
1002 if (status == MagickFalse)
1004 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1005 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,
1006 reconstruct_image->columns,1,exception);
1007 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
1012 indexes=GetCacheViewVirtualIndexQueue(image_view);
1013 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1014 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
1015 for (x=0; x < (ssize_t) image->columns; x++)
1020 if ((channel & RedChannel) != 0)
1022 distance=QuantumScale*fabs(p->red-(double) q->red);
1023 if (distance > channel_distortion[RedChannel])
1024 channel_distortion[RedChannel]=distance;
1025 if (distance > channel_distortion[AllChannels])
1026 channel_distortion[AllChannels]=distance;
1028 if ((channel & GreenChannel) != 0)
1030 distance=QuantumScale*fabs(p->green-(double) q->green);
1031 if (distance > channel_distortion[GreenChannel])
1032 channel_distortion[GreenChannel]=distance;
1033 if (distance > channel_distortion[AllChannels])
1034 channel_distortion[AllChannels]=distance;
1036 if ((channel & BlueChannel) != 0)
1038 distance=QuantumScale*fabs(p->blue-(double) q->blue);
1039 if (distance > channel_distortion[BlueChannel])
1040 channel_distortion[BlueChannel]=distance;
1041 if (distance > channel_distortion[AllChannels])
1042 channel_distortion[AllChannels]=distance;
1044 if (((channel & OpacityChannel) != 0) &&
1045 (image->matte != MagickFalse))
1047 distance=QuantumScale*fabs(p->opacity-(double) q->opacity);
1048 if (distance > channel_distortion[OpacityChannel])
1049 channel_distortion[OpacityChannel]=distance;
1050 if (distance > channel_distortion[AllChannels])
1051 channel_distortion[AllChannels]=distance;
1053 if (((channel & IndexChannel) != 0) &&
1054 (image->colorspace == CMYKColorspace) &&
1055 (reconstruct_image->colorspace == CMYKColorspace))
1057 distance=QuantumScale*fabs(indexes[x]-(double)
1058 reconstruct_indexes[x]);
1059 if (distance > channel_distortion[BlackChannel])
1060 channel_distortion[BlackChannel]=distance;
1061 if (distance > channel_distortion[AllChannels])
1062 channel_distortion[AllChannels]=distance;
1067 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1068 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1070 for (i=0; i <= (ssize_t) AllChannels; i++)
1071 if (channel_distortion[i] > distortion[i])
1072 distortion[i]=channel_distortion[i];
1074 reconstruct_view=DestroyCacheView(reconstruct_view);
1075 image_view=DestroyCacheView(image_view);
1079 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
1080 const Image *reconstruct_image,const ChannelType channel,
1081 double *distortion,ExceptionInfo *exception)
1086 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1088 if ((channel & RedChannel) != 0)
1089 distortion[RedChannel]=20.0*log10((double) 1.0/sqrt(
1090 distortion[RedChannel]));
1091 if ((channel & GreenChannel) != 0)
1092 distortion[GreenChannel]=20.0*log10((double) 1.0/sqrt(
1093 distortion[GreenChannel]));
1094 if ((channel & BlueChannel) != 0)
1095 distortion[BlueChannel]=20.0*log10((double) 1.0/sqrt(
1096 distortion[BlueChannel]));
1097 if (((channel & OpacityChannel) != 0) &&
1098 (image->matte != MagickFalse))
1099 distortion[OpacityChannel]=20.0*log10((double) 1.0/sqrt(
1100 distortion[OpacityChannel]));
1101 if (((channel & IndexChannel) != 0) &&
1102 (image->colorspace == CMYKColorspace))
1103 distortion[BlackChannel]=20.0*log10((double) 1.0/sqrt(
1104 distortion[BlackChannel]));
1105 distortion[AllChannels]=20.0*log10((double) 1.0/sqrt(
1106 distortion[AllChannels]));
1110 static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
1111 const Image *reconstruct_image,const ChannelType channel,
1112 double *distortion,ExceptionInfo *exception)
1117 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1119 if ((channel & RedChannel) != 0)
1120 distortion[RedChannel]=sqrt(distortion[RedChannel]);
1121 if ((channel & GreenChannel) != 0)
1122 distortion[GreenChannel]=sqrt(distortion[GreenChannel]);
1123 if ((channel & BlueChannel) != 0)
1124 distortion[BlueChannel]=sqrt(distortion[BlueChannel]);
1125 if (((channel & OpacityChannel) != 0) &&
1126 (image->matte != MagickFalse))
1127 distortion[OpacityChannel]=sqrt(distortion[OpacityChannel]);
1128 if (((channel & IndexChannel) != 0) &&
1129 (image->colorspace == CMYKColorspace))
1130 distortion[BlackChannel]=sqrt(distortion[BlackChannel]);
1131 distortion[AllChannels]=sqrt(distortion[AllChannels]);
1135 MagickExport MagickBooleanType GetImageChannelDistortion(Image *image,
1136 const Image *reconstruct_image,const ChannelType channel,
1137 const MetricType metric,double *distortion,ExceptionInfo *exception)
1140 *channel_distortion;
1148 assert(image != (Image *) NULL);
1149 assert(image->signature == MagickSignature);
1150 if (image->debug != MagickFalse)
1151 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1152 assert(reconstruct_image != (const Image *) NULL);
1153 assert(reconstruct_image->signature == MagickSignature);
1154 assert(distortion != (double *) NULL);
1156 if (image->debug != MagickFalse)
1157 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1158 if ((reconstruct_image->columns != image->columns) ||
1159 (reconstruct_image->rows != image->rows))
1160 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1162 Get image distortion.
1164 length=AllChannels+1UL;
1165 channel_distortion=(double *) AcquireQuantumMemory(length,
1166 sizeof(*channel_distortion));
1167 if (channel_distortion == (double *) NULL)
1168 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1169 (void) ResetMagickMemory(channel_distortion,0,length*
1170 sizeof(*channel_distortion));
1173 case AbsoluteErrorMetric:
1175 status=GetAbsoluteDistortion(image,reconstruct_image,channel,
1176 channel_distortion,exception);
1179 case MeanAbsoluteErrorMetric:
1181 status=GetMeanAbsoluteDistortion(image,reconstruct_image,channel,
1182 channel_distortion,exception);
1185 case MeanErrorPerPixelMetric:
1187 status=GetMeanErrorPerPixel(image,reconstruct_image,channel,
1188 channel_distortion,exception);
1191 case MeanSquaredErrorMetric:
1193 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,
1194 channel_distortion,exception);
1197 case NormalizedCrossCorrelationErrorMetric:
1200 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1201 channel,channel_distortion,exception);
1204 case PeakAbsoluteErrorMetric:
1206 status=GetPeakAbsoluteDistortion(image,reconstruct_image,channel,
1207 channel_distortion,exception);
1210 case PeakSignalToNoiseRatioMetric:
1212 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,channel,
1213 channel_distortion,exception);
1216 case RootMeanSquaredErrorMetric:
1218 status=GetRootMeanSquaredDistortion(image,reconstruct_image,channel,
1219 channel_distortion,exception);
1223 *distortion=channel_distortion[AllChannels];
1224 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1229 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1233 % G e t I m a g e C h a n n e l D i s t o r t i o n s %
1237 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1239 % GetImageChannelDistrortion() compares the image channels of an image to a
1240 % reconstructed image and returns the specified distortion metric for each
1243 % The format of the CompareImageChannels method is:
1245 % double *GetImageChannelDistortions(const Image *image,
1246 % const Image *reconstruct_image,const MetricType metric,
1247 % ExceptionInfo *exception)
1249 % A description of each parameter follows:
1251 % o image: the image.
1253 % o reconstruct_image: the reconstruct image.
1255 % o metric: the metric.
1257 % o exception: return any errors or warnings in this structure.
1260 MagickExport double *GetImageChannelDistortions(Image *image,
1261 const Image *reconstruct_image,const MetricType metric,
1262 ExceptionInfo *exception)
1265 *channel_distortion;
1273 assert(image != (Image *) NULL);
1274 assert(image->signature == MagickSignature);
1275 if (image->debug != MagickFalse)
1276 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1277 assert(reconstruct_image != (const Image *) NULL);
1278 assert(reconstruct_image->signature == MagickSignature);
1279 if (image->debug != MagickFalse)
1280 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1281 if ((reconstruct_image->columns != image->columns) ||
1282 (reconstruct_image->rows != image->rows))
1284 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1285 ImageError,"ImageSizeDiffers","`%s'",image->filename);
1286 return((double *) NULL);
1289 Get image distortion.
1291 length=AllChannels+1UL;
1292 channel_distortion=(double *) AcquireQuantumMemory(length,
1293 sizeof(*channel_distortion));
1294 if (channel_distortion == (double *) NULL)
1295 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1296 (void) ResetMagickMemory(channel_distortion,0,length*
1297 sizeof(*channel_distortion));
1300 case AbsoluteErrorMetric:
1302 status=GetAbsoluteDistortion(image,reconstruct_image,AllChannels,
1303 channel_distortion,exception);
1306 case MeanAbsoluteErrorMetric:
1308 status=GetMeanAbsoluteDistortion(image,reconstruct_image,AllChannels,
1309 channel_distortion,exception);
1312 case MeanErrorPerPixelMetric:
1314 status=GetMeanErrorPerPixel(image,reconstruct_image,AllChannels,
1315 channel_distortion,exception);
1318 case MeanSquaredErrorMetric:
1320 status=GetMeanSquaredDistortion(image,reconstruct_image,AllChannels,
1321 channel_distortion,exception);
1324 case NormalizedCrossCorrelationErrorMetric:
1327 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1328 AllChannels,channel_distortion,exception);
1331 case PeakAbsoluteErrorMetric:
1333 status=GetPeakAbsoluteDistortion(image,reconstruct_image,AllChannels,
1334 channel_distortion,exception);
1337 case PeakSignalToNoiseRatioMetric:
1339 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,AllChannels,
1340 channel_distortion,exception);
1343 case RootMeanSquaredErrorMetric:
1345 status=GetRootMeanSquaredDistortion(image,reconstruct_image,AllChannels,
1346 channel_distortion,exception);
1350 return(channel_distortion);
1354 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1358 % I s I m a g e s E q u a l %
1362 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1364 % IsImagesEqual() measures the difference between colors at each pixel
1365 % location of two images. A value other than 0 means the colors match
1366 % exactly. Otherwise an error measure is computed by summing over all
1367 % pixels in an image the distance squared in RGB space between each image
1368 % pixel and its corresponding pixel in the reconstruct image. The error
1369 % measure is assigned to these image members:
1371 % o mean_error_per_pixel: The mean error for any single pixel in
1374 % o normalized_mean_error: The normalized mean quantization error for
1375 % any single pixel in the image. This distance measure is normalized to
1376 % a range between 0 and 1. It is independent of the range of red, green,
1377 % and blue values in the image.
1379 % o normalized_maximum_error: The normalized maximum quantization
1380 % error for any single pixel in the image. This distance measure is
1381 % normalized to a range between 0 and 1. It is independent of the range
1382 % of red, green, and blue values in your image.
1384 % A small normalized mean square error, accessed as
1385 % image->normalized_mean_error, suggests the images are very similar in
1386 % spatial layout and color.
1388 % The format of the IsImagesEqual method is:
1390 % MagickBooleanType IsImagesEqual(Image *image,
1391 % const Image *reconstruct_image)
1393 % A description of each parameter follows.
1395 % o image: the image.
1397 % o reconstruct_image: the reconstruct image.
1400 MagickExport MagickBooleanType IsImagesEqual(Image *image,
1401 const Image *reconstruct_image)
1420 mean_error_per_pixel;
1422 assert(image != (Image *) NULL);
1423 assert(image->signature == MagickSignature);
1424 assert(reconstruct_image != (const Image *) NULL);
1425 assert(reconstruct_image->signature == MagickSignature);
1426 if ((reconstruct_image->columns != image->columns) ||
1427 (reconstruct_image->rows != image->rows))
1428 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1431 mean_error_per_pixel=0.0;
1433 exception=(&image->exception);
1434 image_view=AcquireCacheView(image);
1435 reconstruct_view=AcquireCacheView(reconstruct_image);
1436 for (y=0; y < (ssize_t) image->rows; y++)
1438 register const IndexPacket
1440 *restrict reconstruct_indexes;
1442 register const PixelPacket
1449 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1450 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1452 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
1454 indexes=GetCacheViewVirtualIndexQueue(image_view);
1455 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1456 for (x=0; x < (ssize_t) image->columns; x++)
1461 distance=fabs(p->red-(double) q->red);
1462 mean_error_per_pixel+=distance;
1463 mean_error+=distance*distance;
1464 if (distance > maximum_error)
1465 maximum_error=distance;
1467 distance=fabs(p->green-(double) q->green);
1468 mean_error_per_pixel+=distance;
1469 mean_error+=distance*distance;
1470 if (distance > maximum_error)
1471 maximum_error=distance;
1473 distance=fabs(p->blue-(double) q->blue);
1474 mean_error_per_pixel+=distance;
1475 mean_error+=distance*distance;
1476 if (distance > maximum_error)
1477 maximum_error=distance;
1479 if (image->matte != MagickFalse)
1481 distance=fabs(p->opacity-(double) q->opacity);
1482 mean_error_per_pixel+=distance;
1483 mean_error+=distance*distance;
1484 if (distance > maximum_error)
1485 maximum_error=distance;
1488 if ((image->colorspace == CMYKColorspace) &&
1489 (reconstruct_image->colorspace == CMYKColorspace))
1491 distance=fabs(indexes[x]-(double) reconstruct_indexes[x]);
1492 mean_error_per_pixel+=distance;
1493 mean_error+=distance*distance;
1494 if (distance > maximum_error)
1495 maximum_error=distance;
1502 reconstruct_view=DestroyCacheView(reconstruct_view);
1503 image_view=DestroyCacheView(image_view);
1504 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1505 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1507 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1508 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1513 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1517 % S i m i l a r i t y I m a g e %
1521 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1523 % SimilarityImage() compares the reference image of the image and returns the
1524 % best match offset. In addition, it returns a similarity image such that an
1525 % exact match location is completely white and if none of the pixels match,
1526 % black, otherwise some gray level in-between.
1528 % The format of the SimilarityImageImage method is:
1530 % Image *SimilarityImage(const Image *image,const Image *reference,
1531 % RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
1533 % A description of each parameter follows:
1535 % o image: the image.
1537 % o reference: find an area of the image that closely resembles this image.
1539 % o the best match offset of the reference image within the image.
1541 % o similarity: the computed similarity between the images.
1543 % o exception: return any errors or warnings in this structure.
1547 static double GetNCCDistortion(const Image *image,
1548 const Image *reconstruct_image,
1549 const ChannelStatistics *reconstruct_statistics,ExceptionInfo *exception)
1551 #define SimilarityImageTag "Similarity/Image"
1579 image_statistics=GetImageChannelStatistics(image,exception);
1582 area=1.0/((MagickRealType) image->columns*image->rows);
1583 image_view=AcquireCacheView(image);
1584 reconstruct_view=AcquireCacheView(reconstruct_image);
1585 for (y=0; y < (ssize_t) image->rows; y++)
1587 register const IndexPacket
1589 *restrict reconstruct_indexes;
1591 register const PixelPacket
1598 if (status == MagickFalse)
1600 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1601 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1603 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
1608 indexes=GetCacheViewVirtualIndexQueue(image_view);
1609 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1610 for (x=0; x < (ssize_t) image->columns; x++)
1612 distortion+=area*QuantumScale*(p->red-
1613 image_statistics[RedChannel].mean)*(q->red-
1614 reconstruct_statistics[RedChannel].mean);
1615 distortion+=area*QuantumScale*(p->green-
1616 image_statistics[GreenChannel].mean)*(q->green-
1617 reconstruct_statistics[GreenChannel].mean);
1618 distortion+=area*QuantumScale*(p->blue-
1619 image_statistics[BlueChannel].mean)*(q->blue-
1620 reconstruct_statistics[BlueChannel].mean);
1621 if (image->matte != MagickFalse)
1622 distortion+=area*QuantumScale*(p->opacity-
1623 image_statistics[OpacityChannel].mean)*(q->opacity-
1624 reconstruct_statistics[OpacityChannel].mean);
1625 if ((image->colorspace == CMYKColorspace) &&
1626 (reconstruct_image->colorspace == CMYKColorspace))
1627 distortion+=area*QuantumScale*(indexes[x]-
1628 image_statistics[OpacityChannel].mean)*(reconstruct_indexes[x]-
1629 reconstruct_statistics[OpacityChannel].mean);
1634 reconstruct_view=DestroyCacheView(reconstruct_view);
1635 image_view=DestroyCacheView(image_view);
1637 Divide by the standard deviation.
1639 alpha=image_statistics[AllChannels].standard_deviation*
1640 reconstruct_statistics[AllChannels].standard_deviation;
1641 distortion=QuantumRange*distortion/alpha;
1643 if (image->matte != MagickFalse)
1645 if (image->colorspace == CMYKColorspace)
1647 distortion=sqrt(distortion/number_channels);
1648 if (fabs(alpha) <= MagickEpsilon)
1653 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1655 return(1.0-distortion);
1658 static double GetSimilarityMetric(const Image *image,const Image *reference,
1659 const ChannelStatistics *reference_statistics,const ssize_t x_offset,
1660 const ssize_t y_offset,ExceptionInfo *exception)
1671 SetGeometry(reference,&geometry);
1672 geometry.x=x_offset;
1673 geometry.y=y_offset;
1674 similarity_image=CropImage(image,&geometry,exception);
1675 if (similarity_image == (Image *) NULL)
1677 distortion=GetNCCDistortion(reference,similarity_image,reference_statistics,
1679 similarity_image=DestroyImage(similarity_image);
1683 MagickExport Image *SimilarityImage(Image *image,const Image *reference,
1684 RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
1686 #define SimilarityImageTag "Similarity/Image"
1692 *reference_statistics;
1706 assert(image != (const Image *) NULL);
1707 assert(image->signature == MagickSignature);
1708 if (image->debug != MagickFalse)
1709 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1710 assert(exception != (ExceptionInfo *) NULL);
1711 assert(exception->signature == MagickSignature);
1712 assert(offset != (RectangleInfo *) NULL);
1713 SetGeometry(reference,offset);
1714 *similarity_metric=1.0;
1715 if ((reference->columns > image->columns) || (reference->rows > image->rows))
1716 ThrowImageException(ImageError,"ImageSizeDiffers");
1717 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1718 image->rows-reference->rows+1,MagickTrue,exception);
1719 if (similarity_image == (Image *) NULL)
1720 return((Image *) NULL);
1721 if (SetImageStorageClass(similarity_image,DirectClass) == MagickFalse)
1723 InheritException(exception,&similarity_image->exception);
1724 similarity_image=DestroyImage(similarity_image);
1725 return((Image *) NULL);
1728 Measure similarity of reference image against image.
1732 reference_statistics=GetImageChannelStatistics(reference,exception);
1733 similarity_view=AcquireCacheView(similarity_image);
1734 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1735 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1737 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
1745 register PixelPacket
1748 if (status == MagickFalse)
1750 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1752 if (q == (const PixelPacket *) NULL)
1757 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
1759 similarity=GetSimilarityMetric(image,reference,reference_statistics,x,y,
1761 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1762 #pragma omp critical (MagickCore_SimilarityImage)
1764 if (similarity < *similarity_metric)
1766 *similarity_metric=similarity;
1770 q->red=ClampToQuantum(QuantumRange-QuantumRange*similarity);
1775 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
1777 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1782 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1783 #pragma omp critical (MagickCore_SimilarityImage)
1785 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1787 if (proceed == MagickFalse)
1791 similarity_view=DestroyCacheView(similarity_view);
1792 reference_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1793 reference_statistics);
1794 return(similarity_image);