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-2011 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,CompositeChannels,
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 == CompositeChannels)
260 if (IsMagickColorSimilar(&pixel,&reconstruct_pixel) == MagickFalse)
261 difference=MagickTrue;
265 if (((channel & RedChannel) != 0) &&
266 (GetRedPixelComponent(p) != GetRedPixelComponent(q)))
267 difference=MagickTrue;
268 if (((channel & GreenChannel) != 0) &&
269 (GetGreenPixelComponent(p) != GetGreenPixelComponent(q)))
270 difference=MagickTrue;
271 if (((channel & BlueChannel) != 0) &&
272 (GetBluePixelComponent(p) != GetBluePixelComponent(q)))
273 difference=MagickTrue;
274 if (((channel & OpacityChannel) != 0) &&
275 (image->matte != MagickFalse) &&
276 (GetOpacityPixelComponent(p) != GetOpacityPixelComponent(q)))
277 difference=MagickTrue;
278 if ((((channel & IndexChannel) != 0) &&
279 (image->colorspace == CMYKColorspace) &&
280 (reconstruct_image->colorspace == CMYKColorspace)) &&
281 (GetIndexPixelComponent(indexes+x) !=
282 GetIndexPixelComponent(reconstruct_indexes+x)))
283 difference=MagickTrue;
285 if (difference != MagickFalse)
286 SetPixelPacket(highlight_image,&highlight,r,highlight_indexes+x);
288 SetPixelPacket(highlight_image,&lowlight,r,highlight_indexes+x);
293 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
294 if (sync == MagickFalse)
297 highlight_view=DestroyCacheView(highlight_view);
298 reconstruct_view=DestroyCacheView(reconstruct_view);
299 image_view=DestroyCacheView(image_view);
300 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0);
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 C h a n n e l D i s t o r t i o n %
316 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
318 % GetImageChannelDistortion() compares one or more image channels of an image
319 % to a reconstructed image and returns the specified distortion metric.
321 % The format of the CompareImageChannels method is:
323 % MagickBooleanType GetImageChannelDistortion(const Image *image,
324 % const Image *reconstruct_image,const ChannelType channel,
325 % const MetricType metric,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 channel: the channel.
335 % o metric: the metric.
337 % o distortion: the computed distortion between the images.
339 % o exception: return any errors or warnings in this structure.
343 MagickExport MagickBooleanType GetImageDistortion(Image *image,
344 const Image *reconstruct_image,const MetricType metric,double *distortion,
345 ExceptionInfo *exception)
350 status=GetImageChannelDistortion(image,reconstruct_image,CompositeChannels,
351 metric,distortion,exception);
355 static MagickBooleanType GetAbsoluteDistortion(const Image *image,
356 const Image *reconstruct_image,const ChannelType channel,double *distortion,
357 ExceptionInfo *exception)
373 Compute the absolute difference in pixels between two images.
376 GetMagickPixelPacket(image,&zero);
377 image_view=AcquireCacheView(image);
378 reconstruct_view=AcquireCacheView(reconstruct_image);
379 #if defined(MAGICKCORE_OPENMP_SUPPORT)
380 #pragma omp parallel for schedule(dynamic,4) shared(status)
382 for (y=0; y < (ssize_t) image->rows; y++)
385 channel_distortion[CompositeChannels+1];
391 register const IndexPacket
393 *restrict reconstruct_indexes;
395 register const PixelPacket
403 if (status == MagickFalse)
405 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
406 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
408 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
413 indexes=GetCacheViewVirtualIndexQueue(image_view);
414 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
416 reconstruct_pixel=pixel;
417 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
418 for (x=0; x < (ssize_t) image->columns; x++)
420 SetMagickPixelPacket(image,p,indexes+x,&pixel);
421 SetMagickPixelPacket(reconstruct_image,q,reconstruct_indexes+x,
423 if (IsMagickColorSimilar(&pixel,&reconstruct_pixel) == MagickFalse)
425 if ((channel & RedChannel) != 0)
426 channel_distortion[RedChannel]++;
427 if ((channel & GreenChannel) != 0)
428 channel_distortion[GreenChannel]++;
429 if ((channel & BlueChannel) != 0)
430 channel_distortion[BlueChannel]++;
431 if (((channel & OpacityChannel) != 0) &&
432 (image->matte != MagickFalse))
433 channel_distortion[OpacityChannel]++;
434 if (((channel & IndexChannel) != 0) &&
435 (image->colorspace == CMYKColorspace))
436 channel_distortion[BlackChannel]++;
437 channel_distortion[CompositeChannels]++;
442 #if defined(MAGICKCORE_OPENMP_SUPPORT)
443 #pragma omp critical (MagickCore_GetAbsoluteError)
445 for (i=0; i <= (ssize_t) CompositeChannels; i++)
446 distortion[i]+=channel_distortion[i];
448 reconstruct_view=DestroyCacheView(reconstruct_view);
449 image_view=DestroyCacheView(image_view);
453 static size_t GetNumberChannels(const Image *image,
454 const ChannelType channel)
460 if ((channel & RedChannel) != 0)
462 if ((channel & GreenChannel) != 0)
464 if ((channel & BlueChannel) != 0)
466 if (((channel & OpacityChannel) != 0) &&
467 (image->matte != MagickFalse))
469 if (((channel & IndexChannel) != 0) &&
470 (image->colorspace == CMYKColorspace))
475 static MagickBooleanType GetFuzzDistortion(const Image *image,
476 const Image *reconstruct_image,const ChannelType channel,
477 double *distortion,ExceptionInfo *exception)
493 image_view=AcquireCacheView(image);
494 reconstruct_view=AcquireCacheView(reconstruct_image);
495 #if defined(MAGICKCORE_OPENMP_SUPPORT)
496 #pragma omp parallel for schedule(dynamic,4) shared(status)
498 for (y=0; y < (ssize_t) image->rows; y++)
501 channel_distortion[CompositeChannels+1];
503 register const IndexPacket
505 *restrict reconstruct_indexes;
507 register const PixelPacket
515 if (status == MagickFalse)
517 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
518 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
520 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
525 indexes=GetCacheViewVirtualIndexQueue(image_view);
526 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
527 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
528 for (x=0; x < (ssize_t) image->columns; x++)
533 if ((channel & RedChannel) != 0)
535 distance=QuantumScale*(GetRedPixelComponent(p)-(MagickRealType)
536 GetRedPixelComponent(q));
537 channel_distortion[RedChannel]+=distance*distance;
538 channel_distortion[CompositeChannels]+=distance*distance;
540 if ((channel & GreenChannel) != 0)
542 distance=QuantumScale*(GetGreenPixelComponent(p)-(MagickRealType)
543 GetGreenPixelComponent(q));
544 channel_distortion[GreenChannel]+=distance*distance;
545 channel_distortion[CompositeChannels]+=distance*distance;
547 if ((channel & BlueChannel) != 0)
549 distance=QuantumScale*(GetBluePixelComponent(p)-(MagickRealType)
550 GetBluePixelComponent(q));
551 channel_distortion[BlueChannel]+=distance*distance;
552 channel_distortion[CompositeChannels]+=distance*distance;
554 if (((channel & OpacityChannel) != 0) && ((image->matte != MagickFalse) ||
555 (reconstruct_image->matte != MagickFalse)))
557 distance=QuantumScale*((image->matte != MagickFalse ?
558 GetOpacityPixelComponent(p) : OpaqueOpacity)-
559 (reconstruct_image->matte != MagickFalse ?
560 GetOpacityPixelComponent(q): OpaqueOpacity));
561 channel_distortion[OpacityChannel]+=distance*distance;
562 channel_distortion[CompositeChannels]+=distance*distance;
564 if (((channel & IndexChannel) != 0) &&
565 (image->colorspace == CMYKColorspace) &&
566 (reconstruct_image->colorspace == CMYKColorspace))
568 distance=QuantumScale*(GetIndexPixelComponent(indexes+x)-
569 (MagickRealType) GetIndexPixelComponent(reconstruct_indexes+x));
570 channel_distortion[BlackChannel]+=distance*distance;
571 channel_distortion[CompositeChannels]+=distance*distance;
576 #if defined(MAGICKCORE_OPENMP_SUPPORT)
577 #pragma omp critical (MagickCore_GetMeanSquaredError)
579 for (i=0; i <= (ssize_t) CompositeChannels; i++)
580 distortion[i]+=channel_distortion[i];
582 reconstruct_view=DestroyCacheView(reconstruct_view);
583 image_view=DestroyCacheView(image_view);
584 for (i=0; i <= (ssize_t) CompositeChannels; i++)
585 distortion[i]/=((double) image->columns*image->rows);
586 if (((channel & OpacityChannel) != 0) && ((image->matte != MagickFalse) ||
587 (reconstruct_image->matte != MagickFalse)))
588 distortion[CompositeChannels]/=(double) (GetNumberChannels(image,channel)-1);
590 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
591 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]);
595 static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
596 const Image *reconstruct_image,const ChannelType channel,
597 double *distortion,ExceptionInfo *exception)
613 image_view=AcquireCacheView(image);
614 reconstruct_view=AcquireCacheView(reconstruct_image);
615 #if defined(MAGICKCORE_OPENMP_SUPPORT)
616 #pragma omp parallel for schedule(dynamic,4) shared(status)
618 for (y=0; y < (ssize_t) image->rows; y++)
621 channel_distortion[CompositeChannels+1];
623 register const IndexPacket
625 *restrict reconstruct_indexes;
627 register const PixelPacket
635 if (status == MagickFalse)
637 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
638 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,
639 reconstruct_image->columns,1,exception);
640 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
645 indexes=GetCacheViewVirtualIndexQueue(image_view);
646 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
647 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
648 for (x=0; x < (ssize_t) image->columns; x++)
653 if ((channel & RedChannel) != 0)
655 distance=QuantumScale*fabs(GetRedPixelComponent(p)-(double)
656 GetRedPixelComponent(q));
657 channel_distortion[RedChannel]+=distance;
658 channel_distortion[CompositeChannels]+=distance;
660 if ((channel & GreenChannel) != 0)
662 distance=QuantumScale*fabs(GetGreenPixelComponent(p)-(double)
663 GetGreenPixelComponent(q));
664 channel_distortion[GreenChannel]+=distance;
665 channel_distortion[CompositeChannels]+=distance;
667 if ((channel & BlueChannel) != 0)
669 distance=QuantumScale*fabs(GetBluePixelComponent(p)-(double)
670 GetBluePixelComponent(q));
671 channel_distortion[BlueChannel]+=distance;
672 channel_distortion[CompositeChannels]+=distance;
674 if (((channel & OpacityChannel) != 0) &&
675 (image->matte != MagickFalse))
677 distance=QuantumScale*fabs(GetOpacityPixelComponent(p)-(double)
678 GetOpacityPixelComponent(q));
679 channel_distortion[OpacityChannel]+=distance;
680 channel_distortion[CompositeChannels]+=distance;
682 if (((channel & IndexChannel) != 0) &&
683 (image->colorspace == CMYKColorspace))
685 distance=QuantumScale*fabs(GetIndexPixelComponent(indexes+x)-(double)
686 GetIndexPixelComponent(reconstruct_indexes+x));
687 channel_distortion[BlackChannel]+=distance;
688 channel_distortion[CompositeChannels]+=distance;
693 #if defined(MAGICKCORE_OPENMP_SUPPORT)
694 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
696 for (i=0; i <= (ssize_t) CompositeChannels; i++)
697 distortion[i]+=channel_distortion[i];
699 reconstruct_view=DestroyCacheView(reconstruct_view);
700 image_view=DestroyCacheView(image_view);
701 for (i=0; i <= (ssize_t) CompositeChannels; i++)
702 distortion[i]/=((double) image->columns*image->rows);
703 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
707 static MagickBooleanType GetMeanErrorPerPixel(Image *image,
708 const Image *reconstruct_image,const ChannelType channel,double *distortion,
709 ExceptionInfo *exception)
734 image_view=AcquireCacheView(image);
735 reconstruct_view=AcquireCacheView(reconstruct_image);
736 for (y=0; y < (ssize_t) image->rows; y++)
738 register const IndexPacket
740 *restrict reconstruct_indexes;
742 register const PixelPacket
749 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
750 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
752 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
757 indexes=GetCacheViewVirtualIndexQueue(image_view);
758 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
759 for (x=0; x < (ssize_t) image->columns; x++)
764 if ((channel & OpacityChannel) != 0)
766 if (image->matte != MagickFalse)
767 alpha=(MagickRealType) (QuantumScale*(GetAlphaPixelComponent(p)));
768 if (reconstruct_image->matte != MagickFalse)
769 beta=(MagickRealType) (QuantumScale*GetAlphaPixelComponent(q));
771 if ((channel & RedChannel) != 0)
773 distance=fabs(alpha*GetRedPixelComponent(p)-beta*
774 GetRedPixelComponent(q));
775 distortion[RedChannel]+=distance;
776 distortion[CompositeChannels]+=distance;
777 mean_error+=distance*distance;
778 if (distance > maximum_error)
779 maximum_error=distance;
782 if ((channel & GreenChannel) != 0)
784 distance=fabs(alpha*GetGreenPixelComponent(p)-beta*
785 GetGreenPixelComponent(q));
786 distortion[GreenChannel]+=distance;
787 distortion[CompositeChannels]+=distance;
788 mean_error+=distance*distance;
789 if (distance > maximum_error)
790 maximum_error=distance;
793 if ((channel & BlueChannel) != 0)
795 distance=fabs(alpha*GetBluePixelComponent(p)-beta*
796 GetBluePixelComponent(q));
797 distortion[BlueChannel]+=distance;
798 distortion[CompositeChannels]+=distance;
799 mean_error+=distance*distance;
800 if (distance > maximum_error)
801 maximum_error=distance;
804 if (((channel & OpacityChannel) != 0) &&
805 (image->matte != MagickFalse))
807 distance=fabs((double) GetOpacityPixelComponent(p)-
808 GetOpacityPixelComponent(q));
809 distortion[OpacityChannel]+=distance;
810 distortion[CompositeChannels]+=distance;
811 mean_error+=distance*distance;
812 if (distance > maximum_error)
813 maximum_error=distance;
816 if (((channel & IndexChannel) != 0) &&
817 (image->colorspace == CMYKColorspace) &&
818 (reconstruct_image->colorspace == CMYKColorspace))
820 distance=fabs(alpha*GetIndexPixelComponent(indexes+x)-beta*
821 GetIndexPixelComponent(reconstruct_indexes+x));
822 distortion[BlackChannel]+=distance;
823 distortion[CompositeChannels]+=distance;
824 mean_error+=distance*distance;
825 if (distance > maximum_error)
826 maximum_error=distance;
833 reconstruct_view=DestroyCacheView(reconstruct_view);
834 image_view=DestroyCacheView(image_view);
835 image->error.mean_error_per_pixel=distortion[CompositeChannels]/area;
836 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
837 image->error.normalized_maximum_error=QuantumScale*maximum_error;
841 static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
842 const Image *reconstruct_image,const ChannelType channel,
843 double *distortion,ExceptionInfo *exception)
859 image_view=AcquireCacheView(image);
860 reconstruct_view=AcquireCacheView(reconstruct_image);
861 #if defined(MAGICKCORE_OPENMP_SUPPORT)
862 #pragma omp parallel for schedule(dynamic,4) shared(status)
864 for (y=0; y < (ssize_t) image->rows; y++)
867 channel_distortion[CompositeChannels+1];
869 register const IndexPacket
871 *restrict reconstruct_indexes;
873 register const PixelPacket
881 if (status == MagickFalse)
883 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
884 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,
885 reconstruct_image->columns,1,exception);
886 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
891 indexes=GetCacheViewVirtualIndexQueue(image_view);
892 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
893 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
894 for (x=0; x < (ssize_t) image->columns; x++)
899 if ((channel & RedChannel) != 0)
901 distance=QuantumScale*(GetRedPixelComponent(p)-(MagickRealType)
902 GetRedPixelComponent(q));
903 channel_distortion[RedChannel]+=distance*distance;
904 channel_distortion[CompositeChannels]+=distance*distance;
906 if ((channel & GreenChannel) != 0)
908 distance=QuantumScale*(GetGreenPixelComponent(p)-(MagickRealType)
909 GetGreenPixelComponent(q));
910 channel_distortion[GreenChannel]+=distance*distance;
911 channel_distortion[CompositeChannels]+=distance*distance;
913 if ((channel & BlueChannel) != 0)
915 distance=QuantumScale*(GetBluePixelComponent(p)-(MagickRealType)
916 GetBluePixelComponent(q));
917 channel_distortion[BlueChannel]+=distance*distance;
918 channel_distortion[CompositeChannels]+=distance*distance;
920 if (((channel & OpacityChannel) != 0) &&
921 (image->matte != MagickFalse))
923 distance=QuantumScale*(GetOpacityPixelComponent(p)-(MagickRealType)
924 GetOpacityPixelComponent(q));
925 channel_distortion[OpacityChannel]+=distance*distance;
926 channel_distortion[CompositeChannels]+=distance*distance;
928 if (((channel & IndexChannel) != 0) &&
929 (image->colorspace == CMYKColorspace) &&
930 (reconstruct_image->colorspace == CMYKColorspace))
932 distance=QuantumScale*(GetIndexPixelComponent(indexes+x)-
933 (MagickRealType) GetIndexPixelComponent(reconstruct_indexes+x));
934 channel_distortion[BlackChannel]+=distance*distance;
935 channel_distortion[CompositeChannels]+=distance*distance;
940 #if defined(MAGICKCORE_OPENMP_SUPPORT)
941 #pragma omp critical (MagickCore_GetMeanSquaredError)
943 for (i=0; i <= (ssize_t) CompositeChannels; i++)
944 distortion[i]+=channel_distortion[i];
946 reconstruct_view=DestroyCacheView(reconstruct_view);
947 image_view=DestroyCacheView(image_view);
948 for (i=0; i <= (ssize_t) CompositeChannels; i++)
949 distortion[i]/=((double) image->columns*image->rows);
950 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
954 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
955 const Image *image,const Image *reconstruct_image,const ChannelType channel,
956 double *distortion,ExceptionInfo *exception)
958 #define SimilarityImageTag "Similarity/Image"
966 *reconstruct_statistics;
984 Normalize to account for variation due to lighting and exposure condition.
986 image_statistics=GetImageChannelStatistics(image,exception);
987 reconstruct_statistics=GetImageChannelStatistics(reconstruct_image,exception);
990 for (i=0; i <= (ssize_t) CompositeChannels; i++)
992 area=1.0/((MagickRealType) image->columns*image->rows);
993 image_view=AcquireCacheView(image);
994 reconstruct_view=AcquireCacheView(reconstruct_image);
995 for (y=0; y < (ssize_t) image->rows; y++)
997 register const IndexPacket
999 *restrict reconstruct_indexes;
1001 register const PixelPacket
1008 if (status == MagickFalse)
1010 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1011 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1013 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
1018 indexes=GetCacheViewVirtualIndexQueue(image_view);
1019 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1020 for (x=0; x < (ssize_t) image->columns; x++)
1022 if ((channel & RedChannel) != 0)
1023 distortion[RedChannel]+=area*QuantumScale*(GetRedPixelComponent(p)-
1024 image_statistics[RedChannel].mean)*(GetRedPixelComponent(q)-
1025 reconstruct_statistics[RedChannel].mean);
1026 if ((channel & GreenChannel) != 0)
1027 distortion[GreenChannel]+=area*QuantumScale*(GetGreenPixelComponent(p)-
1028 image_statistics[GreenChannel].mean)*(GetGreenPixelComponent(q)-
1029 reconstruct_statistics[GreenChannel].mean);
1030 if ((channel & BlueChannel) != 0)
1031 distortion[BlueChannel]+=area*QuantumScale*(GetBluePixelComponent(p)-
1032 image_statistics[BlueChannel].mean)*(GetBluePixelComponent(q)-
1033 reconstruct_statistics[BlueChannel].mean);
1034 if (((channel & OpacityChannel) != 0) &&
1035 (image->matte != MagickFalse))
1036 distortion[OpacityChannel]+=area*QuantumScale*(
1037 GetOpacityPixelComponent(p)-image_statistics[OpacityChannel].mean)*
1038 (GetOpacityPixelComponent(q)-
1039 reconstruct_statistics[OpacityChannel].mean);
1040 if (((channel & IndexChannel) != 0) &&
1041 (image->colorspace == CMYKColorspace) &&
1042 (reconstruct_image->colorspace == CMYKColorspace))
1043 distortion[BlackChannel]+=area*QuantumScale*(
1044 GetIndexPixelComponent(indexes+x)-
1045 image_statistics[OpacityChannel].mean)*(
1046 GetIndexPixelComponent(reconstruct_indexes+x)-
1047 reconstruct_statistics[OpacityChannel].mean);
1051 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1056 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1058 if (proceed == MagickFalse)
1062 reconstruct_view=DestroyCacheView(reconstruct_view);
1063 image_view=DestroyCacheView(image_view);
1065 Divide by the standard deviation.
1067 for (i=0; i < (ssize_t) CompositeChannels; i++)
1072 gamma=image_statistics[i].standard_deviation*
1073 reconstruct_statistics[i].standard_deviation;
1074 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1075 distortion[i]=QuantumRange*gamma*distortion[i];
1077 distortion[CompositeChannels]=0.0;
1078 if ((channel & RedChannel) != 0)
1079 distortion[CompositeChannels]+=distortion[RedChannel]*distortion[RedChannel];
1080 if ((channel & GreenChannel) != 0)
1081 distortion[CompositeChannels]+=distortion[GreenChannel]*distortion[GreenChannel];
1082 if ((channel & BlueChannel) != 0)
1083 distortion[CompositeChannels]+=distortion[BlueChannel]*distortion[BlueChannel];
1084 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1085 distortion[CompositeChannels]+=distortion[OpacityChannel]*
1086 distortion[OpacityChannel];
1087 if (((channel & IndexChannel) != 0) &&
1088 (image->colorspace == CMYKColorspace))
1089 distortion[CompositeChannels]+=distortion[BlackChannel]*distortion[BlackChannel];
1090 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]/GetNumberChannels(image,
1095 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1096 reconstruct_statistics);
1097 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1102 static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
1103 const Image *reconstruct_image,const ChannelType channel,
1104 double *distortion,ExceptionInfo *exception)
1117 image_view=AcquireCacheView(image);
1118 reconstruct_view=AcquireCacheView(reconstruct_image);
1119 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1120 #pragma omp parallel for schedule(dynamic,4) shared(status)
1122 for (y=0; y < (ssize_t) image->rows; y++)
1125 channel_distortion[CompositeChannels+1];
1127 register const IndexPacket
1129 *restrict reconstruct_indexes;
1131 register const PixelPacket
1139 if (status == MagickFalse)
1141 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1142 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,
1143 reconstruct_image->columns,1,exception);
1144 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
1149 indexes=GetCacheViewVirtualIndexQueue(image_view);
1150 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1151 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
1152 for (x=0; x < (ssize_t) image->columns; x++)
1157 if ((channel & RedChannel) != 0)
1159 distance=QuantumScale*fabs(GetRedPixelComponent(p)-(double)
1160 GetRedPixelComponent(q));
1161 if (distance > channel_distortion[RedChannel])
1162 channel_distortion[RedChannel]=distance;
1163 if (distance > channel_distortion[CompositeChannels])
1164 channel_distortion[CompositeChannels]=distance;
1166 if ((channel & GreenChannel) != 0)
1168 distance=QuantumScale*fabs(GetGreenPixelComponent(p)-(double)
1169 GetGreenPixelComponent(q));
1170 if (distance > channel_distortion[GreenChannel])
1171 channel_distortion[GreenChannel]=distance;
1172 if (distance > channel_distortion[CompositeChannels])
1173 channel_distortion[CompositeChannels]=distance;
1175 if ((channel & BlueChannel) != 0)
1177 distance=QuantumScale*fabs(GetBluePixelComponent(p)-(double)
1178 GetBluePixelComponent(q));
1179 if (distance > channel_distortion[BlueChannel])
1180 channel_distortion[BlueChannel]=distance;
1181 if (distance > channel_distortion[CompositeChannels])
1182 channel_distortion[CompositeChannels]=distance;
1184 if (((channel & OpacityChannel) != 0) &&
1185 (image->matte != MagickFalse))
1187 distance=QuantumScale*fabs(GetOpacityPixelComponent(p)-(double)
1188 GetOpacityPixelComponent(q));
1189 if (distance > channel_distortion[OpacityChannel])
1190 channel_distortion[OpacityChannel]=distance;
1191 if (distance > channel_distortion[CompositeChannels])
1192 channel_distortion[CompositeChannels]=distance;
1194 if (((channel & IndexChannel) != 0) &&
1195 (image->colorspace == CMYKColorspace) &&
1196 (reconstruct_image->colorspace == CMYKColorspace))
1198 distance=QuantumScale*fabs(GetIndexPixelComponent(indexes+x)-(double)
1199 GetIndexPixelComponent(reconstruct_indexes+x));
1200 if (distance > channel_distortion[BlackChannel])
1201 channel_distortion[BlackChannel]=distance;
1202 if (distance > channel_distortion[CompositeChannels])
1203 channel_distortion[CompositeChannels]=distance;
1208 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1209 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1211 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1212 if (channel_distortion[i] > distortion[i])
1213 distortion[i]=channel_distortion[i];
1215 reconstruct_view=DestroyCacheView(reconstruct_view);
1216 image_view=DestroyCacheView(image_view);
1220 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
1221 const Image *reconstruct_image,const ChannelType channel,
1222 double *distortion,ExceptionInfo *exception)
1227 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1229 if ((channel & RedChannel) != 0)
1230 distortion[RedChannel]=20.0*log10((double) 1.0/sqrt(
1231 distortion[RedChannel]));
1232 if ((channel & GreenChannel) != 0)
1233 distortion[GreenChannel]=20.0*log10((double) 1.0/sqrt(
1234 distortion[GreenChannel]));
1235 if ((channel & BlueChannel) != 0)
1236 distortion[BlueChannel]=20.0*log10((double) 1.0/sqrt(
1237 distortion[BlueChannel]));
1238 if (((channel & OpacityChannel) != 0) &&
1239 (image->matte != MagickFalse))
1240 distortion[OpacityChannel]=20.0*log10((double) 1.0/sqrt(
1241 distortion[OpacityChannel]));
1242 if (((channel & IndexChannel) != 0) &&
1243 (image->colorspace == CMYKColorspace))
1244 distortion[BlackChannel]=20.0*log10((double) 1.0/sqrt(
1245 distortion[BlackChannel]));
1246 distortion[CompositeChannels]=20.0*log10((double) 1.0/sqrt(
1247 distortion[CompositeChannels]));
1251 static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
1252 const Image *reconstruct_image,const ChannelType channel,
1253 double *distortion,ExceptionInfo *exception)
1258 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1260 if ((channel & RedChannel) != 0)
1261 distortion[RedChannel]=sqrt(distortion[RedChannel]);
1262 if ((channel & GreenChannel) != 0)
1263 distortion[GreenChannel]=sqrt(distortion[GreenChannel]);
1264 if ((channel & BlueChannel) != 0)
1265 distortion[BlueChannel]=sqrt(distortion[BlueChannel]);
1266 if (((channel & OpacityChannel) != 0) &&
1267 (image->matte != MagickFalse))
1268 distortion[OpacityChannel]=sqrt(distortion[OpacityChannel]);
1269 if (((channel & IndexChannel) != 0) &&
1270 (image->colorspace == CMYKColorspace))
1271 distortion[BlackChannel]=sqrt(distortion[BlackChannel]);
1272 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]);
1276 MagickExport MagickBooleanType GetImageChannelDistortion(Image *image,
1277 const Image *reconstruct_image,const ChannelType channel,
1278 const MetricType metric,double *distortion,ExceptionInfo *exception)
1281 *channel_distortion;
1289 assert(image != (Image *) NULL);
1290 assert(image->signature == MagickSignature);
1291 if (image->debug != MagickFalse)
1292 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1293 assert(reconstruct_image != (const Image *) NULL);
1294 assert(reconstruct_image->signature == MagickSignature);
1295 assert(distortion != (double *) NULL);
1297 if (image->debug != MagickFalse)
1298 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1299 if ((reconstruct_image->columns != image->columns) ||
1300 (reconstruct_image->rows != image->rows))
1301 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1303 Get image distortion.
1305 length=CompositeChannels+1UL;
1306 channel_distortion=(double *) AcquireQuantumMemory(length,
1307 sizeof(*channel_distortion));
1308 if (channel_distortion == (double *) NULL)
1309 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1310 (void) ResetMagickMemory(channel_distortion,0,length*
1311 sizeof(*channel_distortion));
1314 case AbsoluteErrorMetric:
1316 status=GetAbsoluteDistortion(image,reconstruct_image,channel,
1317 channel_distortion,exception);
1320 case FuzzErrorMetric:
1322 status=GetFuzzDistortion(image,reconstruct_image,channel,
1323 channel_distortion,exception);
1326 case MeanAbsoluteErrorMetric:
1328 status=GetMeanAbsoluteDistortion(image,reconstruct_image,channel,
1329 channel_distortion,exception);
1332 case MeanErrorPerPixelMetric:
1334 status=GetMeanErrorPerPixel(image,reconstruct_image,channel,
1335 channel_distortion,exception);
1338 case MeanSquaredErrorMetric:
1340 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,
1341 channel_distortion,exception);
1344 case NormalizedCrossCorrelationErrorMetric:
1347 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1348 channel,channel_distortion,exception);
1351 case PeakAbsoluteErrorMetric:
1353 status=GetPeakAbsoluteDistortion(image,reconstruct_image,channel,
1354 channel_distortion,exception);
1357 case PeakSignalToNoiseRatioMetric:
1359 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,channel,
1360 channel_distortion,exception);
1363 case RootMeanSquaredErrorMetric:
1365 status=GetRootMeanSquaredDistortion(image,reconstruct_image,channel,
1366 channel_distortion,exception);
1370 *distortion=channel_distortion[CompositeChannels];
1371 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1376 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1380 % 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 %
1384 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1386 % GetImageChannelDistrortion() compares the image channels of an image to a
1387 % reconstructed image and returns the specified distortion metric for each
1390 % The format of the CompareImageChannels method is:
1392 % double *GetImageChannelDistortions(const Image *image,
1393 % const Image *reconstruct_image,const MetricType metric,
1394 % ExceptionInfo *exception)
1396 % A description of each parameter follows:
1398 % o image: the image.
1400 % o reconstruct_image: the reconstruct image.
1402 % o metric: the metric.
1404 % o exception: return any errors or warnings in this structure.
1407 MagickExport double *GetImageChannelDistortions(Image *image,
1408 const Image *reconstruct_image,const MetricType metric,
1409 ExceptionInfo *exception)
1412 *channel_distortion;
1420 assert(image != (Image *) NULL);
1421 assert(image->signature == MagickSignature);
1422 if (image->debug != MagickFalse)
1423 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1424 assert(reconstruct_image != (const Image *) NULL);
1425 assert(reconstruct_image->signature == MagickSignature);
1426 if (image->debug != MagickFalse)
1427 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1428 if ((reconstruct_image->columns != image->columns) ||
1429 (reconstruct_image->rows != image->rows))
1431 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1432 ImageError,"ImageSizeDiffers","`%s'",image->filename);
1433 return((double *) NULL);
1436 Get image distortion.
1438 length=CompositeChannels+1UL;
1439 channel_distortion=(double *) AcquireQuantumMemory(length,
1440 sizeof(*channel_distortion));
1441 if (channel_distortion == (double *) NULL)
1442 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1443 (void) ResetMagickMemory(channel_distortion,0,length*
1444 sizeof(*channel_distortion));
1448 case AbsoluteErrorMetric:
1450 status=GetAbsoluteDistortion(image,reconstruct_image,CompositeChannels,
1451 channel_distortion,exception);
1454 case FuzzErrorMetric:
1456 status=GetFuzzDistortion(image,reconstruct_image,CompositeChannels,
1457 channel_distortion,exception);
1460 case MeanAbsoluteErrorMetric:
1462 status=GetMeanAbsoluteDistortion(image,reconstruct_image,CompositeChannels,
1463 channel_distortion,exception);
1466 case MeanErrorPerPixelMetric:
1468 status=GetMeanErrorPerPixel(image,reconstruct_image,CompositeChannels,
1469 channel_distortion,exception);
1472 case MeanSquaredErrorMetric:
1474 status=GetMeanSquaredDistortion(image,reconstruct_image,CompositeChannels,
1475 channel_distortion,exception);
1478 case NormalizedCrossCorrelationErrorMetric:
1481 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1482 CompositeChannels,channel_distortion,exception);
1485 case PeakAbsoluteErrorMetric:
1487 status=GetPeakAbsoluteDistortion(image,reconstruct_image,CompositeChannels,
1488 channel_distortion,exception);
1491 case PeakSignalToNoiseRatioMetric:
1493 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,CompositeChannels,
1494 channel_distortion,exception);
1497 case RootMeanSquaredErrorMetric:
1499 status=GetRootMeanSquaredDistortion(image,reconstruct_image,CompositeChannels,
1500 channel_distortion,exception);
1504 if (status == MagickFalse)
1506 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1507 return((double *) NULL);
1509 return(channel_distortion);
1513 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1517 % I s I m a g e s E q u a l %
1521 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1523 % IsImagesEqual() measures the difference between colors at each pixel
1524 % location of two images. A value other than 0 means the colors match
1525 % exactly. Otherwise an error measure is computed by summing over all
1526 % pixels in an image the distance squared in RGB space between each image
1527 % pixel and its corresponding pixel in the reconstruct image. The error
1528 % measure is assigned to these image members:
1530 % o mean_error_per_pixel: The mean error for any single pixel in
1533 % o normalized_mean_error: The normalized mean quantization error for
1534 % any single pixel in the image. This distance measure is normalized to
1535 % a range between 0 and 1. It is independent of the range of red, green,
1536 % and blue values in the image.
1538 % o normalized_maximum_error: The normalized maximum quantization
1539 % error for any single pixel in the image. This distance measure is
1540 % normalized to a range between 0 and 1. It is independent of the range
1541 % of red, green, and blue values in your image.
1543 % A small normalized mean square error, accessed as
1544 % image->normalized_mean_error, suggests the images are very similar in
1545 % spatial layout and color.
1547 % The format of the IsImagesEqual method is:
1549 % MagickBooleanType IsImagesEqual(Image *image,
1550 % const Image *reconstruct_image)
1552 % A description of each parameter follows.
1554 % o image: the image.
1556 % o reconstruct_image: the reconstruct image.
1559 MagickExport MagickBooleanType IsImagesEqual(Image *image,
1560 const Image *reconstruct_image)
1576 mean_error_per_pixel;
1581 assert(image != (Image *) NULL);
1582 assert(image->signature == MagickSignature);
1583 assert(reconstruct_image != (const Image *) NULL);
1584 assert(reconstruct_image->signature == MagickSignature);
1585 if ((reconstruct_image->columns != image->columns) ||
1586 (reconstruct_image->rows != image->rows))
1587 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1590 mean_error_per_pixel=0.0;
1592 exception=(&image->exception);
1593 image_view=AcquireCacheView(image);
1594 reconstruct_view=AcquireCacheView(reconstruct_image);
1595 for (y=0; y < (ssize_t) image->rows; y++)
1597 register const IndexPacket
1599 *restrict reconstruct_indexes;
1601 register const PixelPacket
1608 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1609 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1611 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
1613 indexes=GetCacheViewVirtualIndexQueue(image_view);
1614 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1615 for (x=0; x < (ssize_t) image->columns; x++)
1620 distance=fabs(GetRedPixelComponent(p)-(double)
1621 GetRedPixelComponent(q));
1622 mean_error_per_pixel+=distance;
1623 mean_error+=distance*distance;
1624 if (distance > maximum_error)
1625 maximum_error=distance;
1627 distance=fabs(GetGreenPixelComponent(p)-(double)
1628 GetGreenPixelComponent(q));
1629 mean_error_per_pixel+=distance;
1630 mean_error+=distance*distance;
1631 if (distance > maximum_error)
1632 maximum_error=distance;
1634 distance=fabs(GetBluePixelComponent(p)-(double)
1635 GetBluePixelComponent(q));
1636 mean_error_per_pixel+=distance;
1637 mean_error+=distance*distance;
1638 if (distance > maximum_error)
1639 maximum_error=distance;
1641 if (image->matte != MagickFalse)
1643 distance=fabs(GetOpacityPixelComponent(p)-(double)
1644 GetOpacityPixelComponent(q));
1645 mean_error_per_pixel+=distance;
1646 mean_error+=distance*distance;
1647 if (distance > maximum_error)
1648 maximum_error=distance;
1651 if ((image->colorspace == CMYKColorspace) &&
1652 (reconstruct_image->colorspace == CMYKColorspace))
1654 distance=fabs(GetIndexPixelComponent(indexes+x)-(double)
1655 GetIndexPixelComponent(reconstruct_indexes+x));
1656 mean_error_per_pixel+=distance;
1657 mean_error+=distance*distance;
1658 if (distance > maximum_error)
1659 maximum_error=distance;
1666 reconstruct_view=DestroyCacheView(reconstruct_view);
1667 image_view=DestroyCacheView(image_view);
1668 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1669 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1671 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1672 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1677 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1681 % S i m i l a r i t y I m a g e %
1685 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1687 % SimilarityImage() compares the reference image of the image and returns the
1688 % best match offset. In addition, it returns a similarity image such that an
1689 % exact match location is completely white and if none of the pixels match,
1690 % black, otherwise some gray level in-between.
1692 % The format of the SimilarityImageImage method is:
1694 % Image *SimilarityImage(const Image *image,const Image *reference,
1695 % RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
1697 % A description of each parameter follows:
1699 % o image: the image.
1701 % o reference: find an area of the image that closely resembles this image.
1703 % o the best match offset of the reference image within the image.
1705 % o similarity: the computed similarity between the images.
1707 % o exception: return any errors or warnings in this structure.
1711 static double GetNCCDistortion(const Image *image,
1712 const Image *reconstruct_image,
1713 const ChannelStatistics *reconstruct_statistics,ExceptionInfo *exception)
1715 #define SimilarityImageTag "Similarity/Image"
1741 Normalize to account for variation due to lighting and exposure condition.
1743 image_statistics=GetImageChannelStatistics(image,exception);
1746 area=1.0/((MagickRealType) image->columns*image->rows);
1747 image_view=AcquireCacheView(image);
1748 reconstruct_view=AcquireCacheView(reconstruct_image);
1749 for (y=0; y < (ssize_t) image->rows; y++)
1751 register const IndexPacket
1753 *restrict reconstruct_indexes;
1755 register const PixelPacket
1762 if (status == MagickFalse)
1764 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1765 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1767 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
1772 indexes=GetCacheViewVirtualIndexQueue(image_view);
1773 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1774 for (x=0; x < (ssize_t) image->columns; x++)
1776 distortion+=area*QuantumScale*(GetRedPixelComponent(p)-
1777 image_statistics[RedChannel].mean)*(GetRedPixelComponent(q)-
1778 reconstruct_statistics[RedChannel].mean);
1779 distortion+=area*QuantumScale*(GetGreenPixelComponent(p)-
1780 image_statistics[GreenChannel].mean)*(GetGreenPixelComponent(q)-
1781 reconstruct_statistics[GreenChannel].mean);
1782 distortion+=area*QuantumScale*(GetBluePixelComponent(p)-
1783 image_statistics[BlueChannel].mean)*(q->blue-
1784 reconstruct_statistics[BlueChannel].mean);
1785 if (image->matte != MagickFalse)
1786 distortion+=area*QuantumScale*(GetOpacityPixelComponent(p)-
1787 image_statistics[OpacityChannel].mean)*(GetOpacityPixelComponent(q)-
1788 reconstruct_statistics[OpacityChannel].mean);
1789 if ((image->colorspace == CMYKColorspace) &&
1790 (reconstruct_image->colorspace == CMYKColorspace))
1791 distortion+=area*QuantumScale*(GetIndexPixelComponent(indexes+x)-
1792 image_statistics[OpacityChannel].mean)*(GetIndexPixelComponent(
1793 reconstruct_indexes+x)-reconstruct_statistics[OpacityChannel].mean);
1798 reconstruct_view=DestroyCacheView(reconstruct_view);
1799 image_view=DestroyCacheView(image_view);
1801 Divide by the standard deviation.
1803 gamma=image_statistics[CompositeChannels].standard_deviation*
1804 reconstruct_statistics[CompositeChannels].standard_deviation;
1805 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1806 distortion=QuantumRange*gamma*distortion;
1808 if (image->matte != MagickFalse)
1810 if (image->colorspace == CMYKColorspace)
1812 distortion=sqrt(distortion/number_channels);
1816 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1818 return(1.0-distortion);
1821 static double GetSimilarityMetric(const Image *image,const Image *reference,
1822 const ChannelStatistics *reference_statistics,const ssize_t x_offset,
1823 const ssize_t y_offset,ExceptionInfo *exception)
1834 SetGeometry(reference,&geometry);
1835 geometry.x=x_offset;
1836 geometry.y=y_offset;
1837 similarity_image=CropImage(image,&geometry,exception);
1838 if (similarity_image == (Image *) NULL)
1840 distortion=GetNCCDistortion(reference,similarity_image,reference_statistics,
1842 similarity_image=DestroyImage(similarity_image);
1846 MagickExport Image *SimilarityImage(Image *image,const Image *reference,
1847 RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
1849 #define SimilarityImageTag "Similarity/Image"
1855 *reference_statistics;
1869 assert(image != (const Image *) NULL);
1870 assert(image->signature == MagickSignature);
1871 if (image->debug != MagickFalse)
1872 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1873 assert(exception != (ExceptionInfo *) NULL);
1874 assert(exception->signature == MagickSignature);
1875 assert(offset != (RectangleInfo *) NULL);
1876 SetGeometry(reference,offset);
1877 *similarity_metric=1.0;
1878 if ((reference->columns > image->columns) || (reference->rows > image->rows))
1879 ThrowImageException(ImageError,"ImageSizeDiffers");
1880 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1881 image->rows-reference->rows+1,MagickTrue,exception);
1882 if (similarity_image == (Image *) NULL)
1883 return((Image *) NULL);
1884 if (SetImageStorageClass(similarity_image,DirectClass) == MagickFalse)
1886 InheritException(exception,&similarity_image->exception);
1887 similarity_image=DestroyImage(similarity_image);
1888 return((Image *) NULL);
1891 Measure similarity of reference image against image.
1895 reference_statistics=GetImageChannelStatistics(reference,exception);
1896 similarity_view=AcquireCacheView(similarity_image);
1897 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1898 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1900 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
1908 register PixelPacket
1911 if (status == MagickFalse)
1913 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1915 if (q == (const PixelPacket *) NULL)
1920 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
1922 similarity=GetSimilarityMetric(image,reference,reference_statistics,x,y,
1924 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1925 #pragma omp critical (MagickCore_SimilarityImage)
1927 if (similarity < *similarity_metric)
1929 *similarity_metric=similarity;
1933 SetRedPixelComponent(q,ClampToQuantum(QuantumRange-QuantumRange*
1935 SetGreenPixelComponent(q,GetRedPixelComponent(q));
1936 SetBluePixelComponent(q,GetRedPixelComponent(q));
1939 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
1941 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1946 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1947 #pragma omp critical (MagickCore_SimilarityImage)
1949 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1951 if (proceed == MagickFalse)
1955 similarity_view=DestroyCacheView(similarity_view);
1956 reference_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1957 reference_statistics);
1958 return(similarity_image);