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 "MagickCore/studio.h"
44 #include "MagickCore/artifact.h"
45 #include "MagickCore/cache-view.h"
46 #include "MagickCore/client.h"
47 #include "MagickCore/color.h"
48 #include "MagickCore/color-private.h"
49 #include "MagickCore/colorspace.h"
50 #include "MagickCore/colorspace-private.h"
51 #include "MagickCore/compare.h"
52 #include "MagickCore/composite-private.h"
53 #include "MagickCore/constitute.h"
54 #include "MagickCore/exception-private.h"
55 #include "MagickCore/geometry.h"
56 #include "MagickCore/image-private.h"
57 #include "MagickCore/list.h"
58 #include "MagickCore/log.h"
59 #include "MagickCore/memory_.h"
60 #include "MagickCore/monitor.h"
61 #include "MagickCore/monitor-private.h"
62 #include "MagickCore/option.h"
63 #include "MagickCore/pixel-accessor.h"
64 #include "MagickCore/resource_.h"
65 #include "MagickCore/string_.h"
66 #include "MagickCore/statistic.h"
67 #include "MagickCore/transform.h"
68 #include "MagickCore/utility.h"
69 #include "MagickCore/version.h"
72 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
76 % C o m p a r e I m a g e %
80 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
82 % CompareImages() compares one or more pixel channels of an image to a
83 % reconstructed image and returns the difference image.
85 % The format of the CompareImages method is:
87 % Image *CompareImages(const Image *image,const Image *reconstruct_image,
88 % const MetricType metric,double *distortion,ExceptionInfo *exception)
90 % A description of each parameter follows:
94 % o reconstruct_image: the reconstruct image.
96 % o metric: the metric.
98 % o distortion: the computed distortion between the images.
100 % o exception: return any errors or warnings in this structure.
103 MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
104 const MetricType metric,double *distortion,ExceptionInfo *exception)
129 assert(image != (Image *) NULL);
130 assert(image->signature == MagickSignature);
131 if (image->debug != MagickFalse)
132 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
133 assert(reconstruct_image != (const Image *) NULL);
134 assert(reconstruct_image->signature == MagickSignature);
135 assert(distortion != (double *) NULL);
137 if (image->debug != MagickFalse)
138 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
139 if ((reconstruct_image->columns != image->columns) ||
140 (reconstruct_image->rows != image->rows))
141 ThrowImageException(ImageError,"ImageSizeDiffers");
142 status=GetImageDistortion(image,reconstruct_image,metric,distortion,
144 if (status == MagickFalse)
145 return((Image *) NULL);
146 difference_image=CloneImage(image,0,0,MagickTrue,exception);
147 if (difference_image == (Image *) NULL)
148 return((Image *) NULL);
149 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception);
150 highlight_image=CloneImage(image,image->columns,image->rows,MagickTrue,
152 if (highlight_image == (Image *) NULL)
154 difference_image=DestroyImage(difference_image);
155 return((Image *) NULL);
157 if (SetImageStorageClass(highlight_image,DirectClass,exception) == MagickFalse)
159 difference_image=DestroyImage(difference_image);
160 highlight_image=DestroyImage(highlight_image);
161 return((Image *) NULL);
163 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception);
164 (void) QueryMagickColor("#f1001ecc",&highlight,exception);
165 artifact=GetImageArtifact(image,"highlight-color");
166 if (artifact != (const char *) NULL)
167 (void) QueryMagickColor(artifact,&highlight,exception);
168 (void) QueryMagickColor("#ffffffcc",&lowlight,exception);
169 artifact=GetImageArtifact(image,"lowlight-color");
170 if (artifact != (const char *) NULL)
171 (void) QueryMagickColor(artifact,&lowlight,exception);
172 if (highlight_image->colorspace == CMYKColorspace)
174 ConvertRGBToCMYK(&highlight);
175 ConvertRGBToCMYK(&lowlight);
178 Generate difference image.
181 GetPixelInfo(image,&zero);
182 image_view=AcquireCacheView(image);
183 reconstruct_view=AcquireCacheView(reconstruct_image);
184 highlight_view=AcquireCacheView(highlight_image);
185 #if defined(MAGICKCORE_OPENMP_SUPPORT)
186 #pragma omp parallel for schedule(dynamic,4) shared(status)
188 for (y=0; y < (ssize_t) image->rows; y++)
197 register const Quantum
207 if (status == MagickFalse)
209 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
210 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
212 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,highlight_image->columns,
214 if ((p == (const Quantum *) NULL) ||
215 (q == (Quantum *) NULL) || (r == (Quantum *) NULL))
221 reconstruct_pixel=zero;
222 for (x=0; x < (ssize_t) image->columns; x++)
227 SetPixelInfo(image,p,&pixel);
228 SetPixelInfo(reconstruct_image,q,&reconstruct_pixel);
229 difference=MagickFalse;
230 if (image->sync == MagickTrue)
232 if (IsFuzzyEquivalencePixelInfo(&pixel,&reconstruct_pixel) == MagickFalse)
233 difference=MagickTrue;
237 if (((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) &&
238 (GetPixelRed(image,p) != GetPixelRed(reconstruct_image,q)))
239 difference=MagickTrue;
240 if (((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) &&
241 (GetPixelGreen(image,p) != GetPixelGreen(reconstruct_image,q)))
242 difference=MagickTrue;
243 if (((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) &&
244 (GetPixelBlue(image,p) != GetPixelBlue(reconstruct_image,q)))
245 difference=MagickTrue;
246 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
247 (image->matte != MagickFalse) &&
248 (GetPixelAlpha(image,p) != GetPixelAlpha(reconstruct_image,q)))
249 difference=MagickTrue;
250 if ((((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
251 (image->colorspace == CMYKColorspace) &&
252 (reconstruct_image->colorspace == CMYKColorspace)) &&
253 (GetPixelBlack(image,p) != GetPixelBlack(reconstruct_image,q)))
254 difference=MagickTrue;
256 if (difference != MagickFalse)
257 SetPixelPixelInfo(highlight_image,&highlight,r);
259 SetPixelPixelInfo(highlight_image,&lowlight,r);
260 p+=GetPixelChannels(image);
261 q+=GetPixelChannels(reconstruct_image);
262 r+=GetPixelChannels(highlight_image);
264 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
265 if (sync == MagickFalse)
268 highlight_view=DestroyCacheView(highlight_view);
269 reconstruct_view=DestroyCacheView(reconstruct_view);
270 image_view=DestroyCacheView(image_view);
271 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0);
272 highlight_image=DestroyImage(highlight_image);
273 if (status == MagickFalse)
274 difference_image=DestroyImage(difference_image);
275 return(difference_image);
279 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
283 % G e t I m a g e D i s t o r t i o n %
287 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
289 % GetImageDistortion() compares one or more pixel channels of an image to a
290 % reconstructed image and returns the specified distortion metric.
292 % The format of the CompareImages method is:
294 % MagickBooleanType GetImageDistortion(const Image *image,
295 % const Image *reconstruct_image,const MetricType metric,
296 % double *distortion,ExceptionInfo *exception)
298 % A description of each parameter follows:
300 % o image: the image.
302 % o reconstruct_image: the reconstruct image.
304 % o metric: the metric.
306 % o distortion: the computed distortion between the images.
308 % o exception: return any errors or warnings in this structure.
312 static MagickBooleanType GetAbsoluteDistortion(const Image *image,
313 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
329 Compute the absolute difference in pixels between two images.
332 GetPixelInfo(image,&zero);
333 image_view=AcquireCacheView(image);
334 reconstruct_view=AcquireCacheView(reconstruct_image);
335 #if defined(MAGICKCORE_OPENMP_SUPPORT)
336 #pragma omp parallel for schedule(dynamic,4) shared(status)
338 for (y=0; y < (ssize_t) image->rows; y++)
341 channel_distortion[CompositeChannels+1];
347 register const Quantum
355 if (status == MagickFalse)
357 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
358 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
360 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
366 reconstruct_pixel=pixel;
367 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
368 for (x=0; x < (ssize_t) image->columns; x++)
370 SetPixelInfo(image,p,&pixel);
371 SetPixelInfo(reconstruct_image,q,&reconstruct_pixel);
372 if (IsFuzzyEquivalencePixelInfo(&pixel,&reconstruct_pixel) == MagickFalse)
374 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
375 channel_distortion[RedChannel]++;
376 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
377 channel_distortion[GreenChannel]++;
378 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
379 channel_distortion[BlueChannel]++;
380 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
381 (image->matte != MagickFalse))
382 channel_distortion[OpacityChannel]++;
383 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
384 (image->colorspace == CMYKColorspace))
385 channel_distortion[BlackChannel]++;
386 channel_distortion[CompositeChannels]++;
388 p+=GetPixelChannels(image);
389 q+=GetPixelChannels(reconstruct_image);
391 #if defined(MAGICKCORE_OPENMP_SUPPORT)
392 #pragma omp critical (MagickCore_GetAbsoluteError)
394 for (i=0; i <= (ssize_t) CompositeChannels; i++)
395 distortion[i]+=channel_distortion[i];
397 reconstruct_view=DestroyCacheView(reconstruct_view);
398 image_view=DestroyCacheView(image_view);
402 static size_t GetNumberChannels(const Image *image)
408 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
410 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
412 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
414 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
415 (image->colorspace == CMYKColorspace))
417 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
418 (image->matte != MagickFalse))
423 static MagickBooleanType GetFuzzDistortion(const Image *image,
424 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
440 image_view=AcquireCacheView(image);
441 reconstruct_view=AcquireCacheView(reconstruct_image);
442 #if defined(MAGICKCORE_OPENMP_SUPPORT)
443 #pragma omp parallel for schedule(dynamic,4) shared(status)
445 for (y=0; y < (ssize_t) image->rows; y++)
448 channel_distortion[CompositeChannels+1];
450 register const Quantum
458 if (status == MagickFalse)
460 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
461 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
463 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
468 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
469 for (x=0; x < (ssize_t) image->columns; x++)
474 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
476 distance=QuantumScale*(GetPixelRed(image,p)-(MagickRealType)
477 GetPixelRed(reconstruct_image,q));
478 channel_distortion[RedChannel]+=distance*distance;
479 channel_distortion[CompositeChannels]+=distance*distance;
481 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
483 distance=QuantumScale*(GetPixelGreen(image,p)-(MagickRealType)
484 GetPixelGreen(reconstruct_image,q));
485 channel_distortion[GreenChannel]+=distance*distance;
486 channel_distortion[CompositeChannels]+=distance*distance;
488 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
490 distance=QuantumScale*(GetPixelBlue(image,p)-(MagickRealType)
491 GetPixelBlue(reconstruct_image,q));
492 channel_distortion[BlueChannel]+=distance*distance;
493 channel_distortion[CompositeChannels]+=distance*distance;
495 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) && ((image->matte != MagickFalse) ||
496 (reconstruct_image->matte != MagickFalse)))
498 distance=QuantumScale*((image->matte != MagickFalse ?
499 GetPixelAlpha(image,p) : OpaqueAlpha)-(reconstruct_image->matte !=
500 MagickFalse ? GetPixelAlpha(reconstruct_image,q): OpaqueAlpha));
501 channel_distortion[OpacityChannel]+=distance*distance;
502 channel_distortion[CompositeChannels]+=distance*distance;
504 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
505 (image->colorspace == CMYKColorspace) &&
506 (reconstruct_image->colorspace == CMYKColorspace))
508 distance=QuantumScale*(GetPixelBlack(image,p)-(MagickRealType)
509 GetPixelBlack(reconstruct_image,q));
510 channel_distortion[BlackChannel]+=distance*distance;
511 channel_distortion[CompositeChannels]+=distance*distance;
513 p+=GetPixelChannels(image);
514 q+=GetPixelChannels(reconstruct_image);
516 #if defined(MAGICKCORE_OPENMP_SUPPORT)
517 #pragma omp critical (MagickCore_GetMeanSquaredError)
519 for (i=0; i <= (ssize_t) CompositeChannels; i++)
520 distortion[i]+=channel_distortion[i];
522 reconstruct_view=DestroyCacheView(reconstruct_view);
523 image_view=DestroyCacheView(image_view);
524 for (i=0; i <= (ssize_t) CompositeChannels; i++)
525 distortion[i]/=((double) image->columns*image->rows);
526 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
527 ((image->matte != MagickFalse) ||
528 (reconstruct_image->matte != MagickFalse)))
529 distortion[CompositeChannels]/=(double) (GetNumberChannels(image)-1);
531 distortion[CompositeChannels]/=(double) GetNumberChannels(image);
532 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]);
536 static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
537 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
553 image_view=AcquireCacheView(image);
554 reconstruct_view=AcquireCacheView(reconstruct_image);
555 #if defined(MAGICKCORE_OPENMP_SUPPORT)
556 #pragma omp parallel for schedule(dynamic,4) shared(status)
558 for (y=0; y < (ssize_t) image->rows; y++)
561 channel_distortion[CompositeChannels+1];
563 register const Quantum
571 if (status == MagickFalse)
573 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
574 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,
575 reconstruct_image->columns,1,exception);
576 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
581 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
582 for (x=0; x < (ssize_t) image->columns; x++)
587 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
589 distance=QuantumScale*fabs(GetPixelRed(image,p)-(double)
590 GetPixelRed(reconstruct_image,q));
591 channel_distortion[RedChannel]+=distance;
592 channel_distortion[CompositeChannels]+=distance;
594 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
596 distance=QuantumScale*fabs(GetPixelGreen(image,p)-(double)
597 GetPixelGreen(reconstruct_image,q));
598 channel_distortion[GreenChannel]+=distance;
599 channel_distortion[CompositeChannels]+=distance;
601 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
603 distance=QuantumScale*fabs(GetPixelBlue(image,p)-(double)
604 GetPixelBlue(reconstruct_image,q));
605 channel_distortion[BlueChannel]+=distance;
606 channel_distortion[CompositeChannels]+=distance;
608 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
609 (image->colorspace == CMYKColorspace))
611 distance=QuantumScale*fabs(GetPixelBlack(image,p)-(double)
612 GetPixelBlack(reconstruct_image,q));
613 channel_distortion[BlackChannel]+=distance;
614 channel_distortion[CompositeChannels]+=distance;
616 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
617 (image->matte != MagickFalse))
619 distance=QuantumScale*fabs(GetPixelAlpha(image,p)-(double)
620 GetPixelAlpha(reconstruct_image,q));
621 channel_distortion[OpacityChannel]+=distance;
622 channel_distortion[CompositeChannels]+=distance;
624 p+=GetPixelChannels(image);
625 q+=GetPixelChannels(reconstruct_image);
627 #if defined(MAGICKCORE_OPENMP_SUPPORT)
628 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
630 for (i=0; i <= (ssize_t) CompositeChannels; i++)
631 distortion[i]+=channel_distortion[i];
633 reconstruct_view=DestroyCacheView(reconstruct_view);
634 image_view=DestroyCacheView(image_view);
635 for (i=0; i <= (ssize_t) CompositeChannels; i++)
636 distortion[i]/=((double) image->columns*image->rows);
637 distortion[CompositeChannels]/=(double) GetNumberChannels(image);
641 static MagickBooleanType GetMeanErrorPerPixel(Image *image,
642 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
667 image_view=AcquireCacheView(image);
668 reconstruct_view=AcquireCacheView(reconstruct_image);
669 for (y=0; y < (ssize_t) image->rows; y++)
671 register const Quantum
678 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
679 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
681 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
686 for (x=0; x < (ssize_t) image->columns; x++)
691 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
693 if (image->matte != MagickFalse)
694 alpha=(MagickRealType) (QuantumScale*(
695 GetPixelAlpha(reconstruct_image,p)));
696 if (reconstruct_image->matte != MagickFalse)
697 beta=(MagickRealType) (QuantumScale*
698 GetPixelAlpha(reconstruct_image,q));
700 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
702 distance=fabs(alpha*GetPixelRed(image,p)-beta*
703 GetPixelRed(reconstruct_image,q));
704 distortion[RedChannel]+=distance;
705 distortion[CompositeChannels]+=distance;
706 mean_error+=distance*distance;
707 if (distance > maximum_error)
708 maximum_error=distance;
711 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
713 distance=fabs(alpha*GetPixelGreen(image,p)-beta*
714 GetPixelGreen(reconstruct_image,q));
715 distortion[GreenChannel]+=distance;
716 distortion[CompositeChannels]+=distance;
717 mean_error+=distance*distance;
718 if (distance > maximum_error)
719 maximum_error=distance;
722 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
724 distance=fabs(alpha*GetPixelBlue(image,p)-beta*
725 GetPixelBlue(reconstruct_image,q));
726 distortion[BlueChannel]+=distance;
727 distortion[CompositeChannels]+=distance;
728 mean_error+=distance*distance;
729 if (distance > maximum_error)
730 maximum_error=distance;
733 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
734 (image->colorspace == CMYKColorspace) &&
735 (reconstruct_image->colorspace == CMYKColorspace))
737 distance=fabs(alpha*GetPixelBlack(image,p)-beta*
738 GetPixelBlack(reconstruct_image,q));
739 distortion[BlackChannel]+=distance;
740 distortion[CompositeChannels]+=distance;
741 mean_error+=distance*distance;
742 if (distance > maximum_error)
743 maximum_error=distance;
746 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
747 (image->matte != MagickFalse))
749 distance=fabs((double) GetPixelAlpha(image,p)-
750 GetPixelAlpha(reconstruct_image,q));
751 distortion[OpacityChannel]+=distance;
752 distortion[CompositeChannels]+=distance;
753 mean_error+=distance*distance;
754 if (distance > maximum_error)
755 maximum_error=distance;
758 p+=GetPixelChannels(image);
759 q+=GetPixelChannels(reconstruct_image);
762 reconstruct_view=DestroyCacheView(reconstruct_view);
763 image_view=DestroyCacheView(image_view);
764 image->error.mean_error_per_pixel=distortion[CompositeChannels]/area;
765 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
766 image->error.normalized_maximum_error=QuantumScale*maximum_error;
770 static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
771 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
787 image_view=AcquireCacheView(image);
788 reconstruct_view=AcquireCacheView(reconstruct_image);
789 #if defined(MAGICKCORE_OPENMP_SUPPORT)
790 #pragma omp parallel for schedule(dynamic,4) shared(status)
792 for (y=0; y < (ssize_t) image->rows; y++)
795 channel_distortion[CompositeChannels+1];
797 register const Quantum
805 if (status == MagickFalse)
807 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
808 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,
809 reconstruct_image->columns,1,exception);
810 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
815 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
816 for (x=0; x < (ssize_t) image->columns; x++)
821 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
823 distance=QuantumScale*(GetPixelRed(image,p)-(MagickRealType)
824 GetPixelRed(reconstruct_image,q));
825 channel_distortion[RedChannel]+=distance*distance;
826 channel_distortion[CompositeChannels]+=distance*distance;
828 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
830 distance=QuantumScale*(GetPixelGreen(image,p)-(MagickRealType)
831 GetPixelGreen(reconstruct_image,q));
832 channel_distortion[GreenChannel]+=distance*distance;
833 channel_distortion[CompositeChannels]+=distance*distance;
835 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
837 distance=QuantumScale*(GetPixelBlue(image,p)-(MagickRealType)
838 GetPixelBlue(reconstruct_image,q));
839 channel_distortion[BlueChannel]+=distance*distance;
840 channel_distortion[CompositeChannels]+=distance*distance;
842 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
843 (image->colorspace == CMYKColorspace) &&
844 (reconstruct_image->colorspace == CMYKColorspace))
846 distance=QuantumScale*(GetPixelBlack(image,p)-(MagickRealType)
847 GetPixelBlack(reconstruct_image,q));
848 channel_distortion[BlackChannel]+=distance*distance;
849 channel_distortion[CompositeChannels]+=distance*distance;
851 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
852 (image->matte != MagickFalse))
854 distance=QuantumScale*(GetPixelAlpha(image,p)-(MagickRealType)
855 GetPixelAlpha(reconstruct_image,q));
856 channel_distortion[OpacityChannel]+=distance*distance;
857 channel_distortion[CompositeChannels]+=distance*distance;
859 p+=GetPixelChannels(image);
860 q+=GetPixelChannels(reconstruct_image);
862 #if defined(MAGICKCORE_OPENMP_SUPPORT)
863 #pragma omp critical (MagickCore_GetMeanSquaredError)
865 for (i=0; i <= (ssize_t) CompositeChannels; i++)
866 distortion[i]+=channel_distortion[i];
868 reconstruct_view=DestroyCacheView(reconstruct_view);
869 image_view=DestroyCacheView(image_view);
870 for (i=0; i <= (ssize_t) CompositeChannels; i++)
871 distortion[i]/=((double) image->columns*image->rows);
872 distortion[CompositeChannels]/=(double) GetNumberChannels(image);
876 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
877 const Image *image,const Image *reconstruct_image,double *distortion,
878 ExceptionInfo *exception)
880 #define SimilarityImageTag "Similarity/Image"
888 *reconstruct_statistics;
906 Normalize to account for variation due to lighting and exposure condition.
908 image_statistics=GetImageStatistics(image,exception);
909 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
912 for (i=0; i <= (ssize_t) CompositeChannels; i++)
914 area=1.0/((MagickRealType) image->columns*image->rows);
915 image_view=AcquireCacheView(image);
916 reconstruct_view=AcquireCacheView(reconstruct_image);
917 for (y=0; y < (ssize_t) image->rows; y++)
919 register const Quantum
926 if (status == MagickFalse)
928 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
929 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
931 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
936 for (x=0; x < (ssize_t) image->columns; x++)
938 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
939 distortion[RedChannel]+=area*QuantumScale*(GetPixelRed(image,p)-
940 image_statistics[RedChannel].mean)*(
941 GetPixelRed(reconstruct_image,q)-
942 reconstruct_statistics[RedChannel].mean);
943 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
944 distortion[GreenChannel]+=area*QuantumScale*(GetPixelGreen(image,p)-
945 image_statistics[GreenChannel].mean)*(
946 GetPixelGreen(reconstruct_image,q)-
947 reconstruct_statistics[GreenChannel].mean);
948 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
949 distortion[BlueChannel]+=area*QuantumScale*(GetPixelBlue(image,p)-
950 image_statistics[BlueChannel].mean)*(
951 GetPixelBlue(reconstruct_image,q)-
952 reconstruct_statistics[BlueChannel].mean);
953 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
954 (image->colorspace == CMYKColorspace) &&
955 (reconstruct_image->colorspace == CMYKColorspace))
956 distortion[BlackChannel]+=area*QuantumScale*(GetPixelBlack(image,p)-
957 image_statistics[OpacityChannel].mean)*(
958 GetPixelBlack(reconstruct_image,q)-
959 reconstruct_statistics[OpacityChannel].mean);
960 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
961 (image->matte != MagickFalse))
962 distortion[OpacityChannel]+=area*QuantumScale*(GetPixelAlpha(image,p)-
963 image_statistics[OpacityChannel].mean)*
964 (GetPixelAlpha(reconstruct_image,q)-
965 reconstruct_statistics[OpacityChannel].mean);
966 p+=GetPixelChannels(image);
967 q+=GetPixelChannels(image);
969 if (image->progress_monitor != (MagickProgressMonitor) NULL)
974 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
976 if (proceed == MagickFalse)
980 reconstruct_view=DestroyCacheView(reconstruct_view);
981 image_view=DestroyCacheView(image_view);
983 Divide by the standard deviation.
985 for (i=0; i < (ssize_t) CompositeChannels; i++)
990 gamma=image_statistics[i].standard_deviation*
991 reconstruct_statistics[i].standard_deviation;
992 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
993 distortion[i]=QuantumRange*gamma*distortion[i];
995 distortion[CompositeChannels]=0.0;
996 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
997 distortion[CompositeChannels]+=distortion[RedChannel]*
998 distortion[RedChannel];
999 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1000 distortion[CompositeChannels]+=distortion[GreenChannel]*
1001 distortion[GreenChannel];
1002 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1003 distortion[CompositeChannels]+=distortion[BlueChannel]*
1004 distortion[BlueChannel];
1005 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) && (image->matte != MagickFalse))
1006 distortion[CompositeChannels]+=distortion[OpacityChannel]*
1007 distortion[OpacityChannel];
1008 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
1009 (image->colorspace == CMYKColorspace))
1010 distortion[CompositeChannels]+=distortion[BlackChannel]*
1011 distortion[BlackChannel];
1012 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]/
1013 GetNumberChannels(image));
1017 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1018 reconstruct_statistics);
1019 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1024 static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
1025 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1038 image_view=AcquireCacheView(image);
1039 reconstruct_view=AcquireCacheView(reconstruct_image);
1040 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1041 #pragma omp parallel for schedule(dynamic,4) shared(status)
1043 for (y=0; y < (ssize_t) image->rows; y++)
1046 channel_distortion[CompositeChannels+1];
1048 register const Quantum
1056 if (status == MagickFalse)
1058 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1059 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,
1060 reconstruct_image->columns,1,exception);
1061 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1066 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
1067 for (x=0; x < (ssize_t) image->columns; x++)
1072 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1074 distance=QuantumScale*fabs(GetPixelRed(image,p)-(double)
1075 GetPixelRed(reconstruct_image,q));
1076 if (distance > channel_distortion[RedChannel])
1077 channel_distortion[RedChannel]=distance;
1078 if (distance > channel_distortion[CompositeChannels])
1079 channel_distortion[CompositeChannels]=distance;
1081 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1083 distance=QuantumScale*fabs(GetPixelGreen(image,p)-(double)
1084 GetPixelGreen(reconstruct_image,q));
1085 if (distance > channel_distortion[GreenChannel])
1086 channel_distortion[GreenChannel]=distance;
1087 if (distance > channel_distortion[CompositeChannels])
1088 channel_distortion[CompositeChannels]=distance;
1090 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1092 distance=QuantumScale*fabs(GetPixelBlue(image,p)-(double)
1093 GetPixelBlue(reconstruct_image,q));
1094 if (distance > channel_distortion[BlueChannel])
1095 channel_distortion[BlueChannel]=distance;
1096 if (distance > channel_distortion[CompositeChannels])
1097 channel_distortion[CompositeChannels]=distance;
1099 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
1100 (image->colorspace == CMYKColorspace) &&
1101 (reconstruct_image->colorspace == CMYKColorspace))
1103 distance=QuantumScale*fabs(GetPixelBlack(image,p)-(double)
1104 GetPixelBlack(reconstruct_image,q));
1105 if (distance > channel_distortion[BlackChannel])
1106 channel_distortion[BlackChannel]=distance;
1107 if (distance > channel_distortion[CompositeChannels])
1108 channel_distortion[CompositeChannels]=distance;
1110 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
1111 (image->matte != MagickFalse))
1113 distance=QuantumScale*fabs(GetPixelAlpha(image,p)-(double)
1114 GetPixelAlpha(reconstruct_image,q));
1115 if (distance > channel_distortion[OpacityChannel])
1116 channel_distortion[OpacityChannel]=distance;
1117 if (distance > channel_distortion[CompositeChannels])
1118 channel_distortion[CompositeChannels]=distance;
1120 p+=GetPixelChannels(image);
1121 q+=GetPixelChannels(image);
1123 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1124 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1126 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1127 if (channel_distortion[i] > distortion[i])
1128 distortion[i]=channel_distortion[i];
1130 reconstruct_view=DestroyCacheView(reconstruct_view);
1131 image_view=DestroyCacheView(image_view);
1135 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
1136 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1141 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1142 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1143 distortion[RedChannel]=20.0*log10((double) 1.0/sqrt(
1144 distortion[RedChannel]));
1145 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1146 distortion[GreenChannel]=20.0*log10((double) 1.0/sqrt(
1147 distortion[GreenChannel]));
1148 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1149 distortion[BlueChannel]=20.0*log10((double) 1.0/sqrt(
1150 distortion[BlueChannel]));
1151 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
1152 (image->matte != MagickFalse))
1153 distortion[OpacityChannel]=20.0*log10((double) 1.0/sqrt(
1154 distortion[OpacityChannel]));
1155 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
1156 (image->colorspace == CMYKColorspace))
1157 distortion[BlackChannel]=20.0*log10((double) 1.0/sqrt(
1158 distortion[BlackChannel]));
1159 distortion[CompositeChannels]=20.0*log10((double) 1.0/sqrt(
1160 distortion[CompositeChannels]));
1164 static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
1165 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1170 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1171 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1172 distortion[RedChannel]=sqrt(distortion[RedChannel]);
1173 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1174 distortion[GreenChannel]=sqrt(distortion[GreenChannel]);
1175 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1176 distortion[BlueChannel]=sqrt(distortion[BlueChannel]);
1177 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
1178 (image->colorspace == CMYKColorspace))
1179 distortion[BlackChannel]=sqrt(distortion[BlackChannel]);
1180 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
1181 (image->matte != MagickFalse))
1182 distortion[OpacityChannel]=sqrt(distortion[OpacityChannel]);
1183 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]);
1187 MagickExport MagickBooleanType GetImageDistortion(Image *image,
1188 const Image *reconstruct_image,const MetricType metric,double *distortion,
1189 ExceptionInfo *exception)
1192 *channel_distortion;
1200 assert(image != (Image *) NULL);
1201 assert(image->signature == MagickSignature);
1202 if (image->debug != MagickFalse)
1203 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1204 assert(reconstruct_image != (const Image *) NULL);
1205 assert(reconstruct_image->signature == MagickSignature);
1206 assert(distortion != (double *) NULL);
1208 if (image->debug != MagickFalse)
1209 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1210 if ((reconstruct_image->columns != image->columns) ||
1211 (reconstruct_image->rows != image->rows))
1212 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1214 Get image distortion.
1216 length=CompositeChannels+1UL;
1217 channel_distortion=(double *) AcquireQuantumMemory(length,
1218 sizeof(*channel_distortion));
1219 if (channel_distortion == (double *) NULL)
1220 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1221 (void) ResetMagickMemory(channel_distortion,0,length*
1222 sizeof(*channel_distortion));
1225 case AbsoluteErrorMetric:
1227 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1231 case FuzzErrorMetric:
1233 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1237 case MeanAbsoluteErrorMetric:
1239 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1240 channel_distortion,exception);
1243 case MeanErrorPerPixelMetric:
1245 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1249 case MeanSquaredErrorMetric:
1251 status=GetMeanSquaredDistortion(image,reconstruct_image,
1252 channel_distortion,exception);
1255 case NormalizedCrossCorrelationErrorMetric:
1258 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1259 channel_distortion,exception);
1262 case PeakAbsoluteErrorMetric:
1264 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1265 channel_distortion,exception);
1268 case PeakSignalToNoiseRatioMetric:
1270 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1271 channel_distortion,exception);
1274 case RootMeanSquaredErrorMetric:
1276 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1277 channel_distortion,exception);
1281 *distortion=channel_distortion[CompositeChannels];
1282 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1287 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1291 % G e t I m a g e D i s t o r t i o n s %
1295 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1297 % GetImageDistrortion() compares the pixel channels of an image to a
1298 % reconstructed image and returns the specified distortion metric for each
1301 % The format of the CompareImages method is:
1303 % double *GetImageDistortions(const Image *image,
1304 % const Image *reconstruct_image,const MetricType metric,
1305 % ExceptionInfo *exception)
1307 % A description of each parameter follows:
1309 % o image: the image.
1311 % o reconstruct_image: the reconstruct image.
1313 % o metric: the metric.
1315 % o exception: return any errors or warnings in this structure.
1318 MagickExport double *GetImageDistortions(Image *image,
1319 const Image *reconstruct_image,const MetricType metric,
1320 ExceptionInfo *exception)
1323 *channel_distortion;
1331 assert(image != (Image *) NULL);
1332 assert(image->signature == MagickSignature);
1333 if (image->debug != MagickFalse)
1334 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1335 assert(reconstruct_image != (const Image *) NULL);
1336 assert(reconstruct_image->signature == MagickSignature);
1337 if (image->debug != MagickFalse)
1338 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1339 if ((reconstruct_image->columns != image->columns) ||
1340 (reconstruct_image->rows != image->rows))
1342 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1343 ImageError,"ImageSizeDiffers","`%s'",image->filename);
1344 return((double *) NULL);
1347 Get image distortion.
1349 length=CompositeChannels+1UL;
1350 channel_distortion=(double *) AcquireQuantumMemory(length,
1351 sizeof(*channel_distortion));
1352 if (channel_distortion == (double *) NULL)
1353 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1354 (void) ResetMagickMemory(channel_distortion,0,length*
1355 sizeof(*channel_distortion));
1359 case AbsoluteErrorMetric:
1361 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1365 case FuzzErrorMetric:
1367 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1371 case MeanAbsoluteErrorMetric:
1373 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1374 channel_distortion,exception);
1377 case MeanErrorPerPixelMetric:
1379 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1383 case MeanSquaredErrorMetric:
1385 status=GetMeanSquaredDistortion(image,reconstruct_image,
1386 channel_distortion,exception);
1389 case NormalizedCrossCorrelationErrorMetric:
1392 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1393 channel_distortion,exception);
1396 case PeakAbsoluteErrorMetric:
1398 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1399 channel_distortion,exception);
1402 case PeakSignalToNoiseRatioMetric:
1404 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1405 channel_distortion,exception);
1408 case RootMeanSquaredErrorMetric:
1410 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1411 channel_distortion,exception);
1415 if (status == MagickFalse)
1417 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1418 return((double *) NULL);
1420 return(channel_distortion);
1424 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1428 % I s I m a g e s E q u a l %
1432 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1434 % IsImagesEqual() measures the difference between colors at each pixel
1435 % location of two images. A value other than 0 means the colors match
1436 % exactly. Otherwise an error measure is computed by summing over all
1437 % pixels in an image the distance squared in RGB space between each image
1438 % pixel and its corresponding pixel in the reconstruct image. The error
1439 % measure is assigned to these image members:
1441 % o mean_error_per_pixel: The mean error for any single pixel in
1444 % o normalized_mean_error: The normalized mean quantization error for
1445 % any single pixel in the image. This distance measure is normalized to
1446 % a range between 0 and 1. It is independent of the range of red, green,
1447 % and blue values in the image.
1449 % o normalized_maximum_error: The normalized maximum quantization
1450 % error for any single pixel in the image. This distance measure is
1451 % normalized to a range between 0 and 1. It is independent of the range
1452 % of red, green, and blue values in your image.
1454 % A small normalized mean square error, accessed as
1455 % image->normalized_mean_error, suggests the images are very similar in
1456 % spatial layout and color.
1458 % The format of the IsImagesEqual method is:
1460 % MagickBooleanType IsImagesEqual(Image *image,
1461 % const Image *reconstruct_image,ExceptionInfo *exception)
1463 % A description of each parameter follows.
1465 % o image: the image.
1467 % o reconstruct_image: the reconstruct image.
1469 % o exception: return any errors or warnings in this structure.
1472 MagickExport MagickBooleanType IsImagesEqual(Image *image,
1473 const Image *reconstruct_image,ExceptionInfo *exception)
1486 mean_error_per_pixel;
1491 assert(image != (Image *) NULL);
1492 assert(image->signature == MagickSignature);
1493 assert(reconstruct_image != (const Image *) NULL);
1494 assert(reconstruct_image->signature == MagickSignature);
1495 if ((reconstruct_image->columns != image->columns) ||
1496 (reconstruct_image->rows != image->rows))
1497 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1500 mean_error_per_pixel=0.0;
1502 image_view=AcquireCacheView(image);
1503 reconstruct_view=AcquireCacheView(reconstruct_image);
1504 for (y=0; y < (ssize_t) image->rows; y++)
1506 register const Quantum
1513 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1514 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1516 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1518 for (x=0; x < (ssize_t) image->columns; x++)
1523 distance=fabs(GetPixelRed(image,p)-(double)
1524 GetPixelRed(reconstruct_image,q));
1525 mean_error_per_pixel+=distance;
1526 mean_error+=distance*distance;
1527 if (distance > maximum_error)
1528 maximum_error=distance;
1530 distance=fabs(GetPixelGreen(image,p)-(double)
1531 GetPixelGreen(reconstruct_image,q));
1532 mean_error_per_pixel+=distance;
1533 mean_error+=distance*distance;
1534 if (distance > maximum_error)
1535 maximum_error=distance;
1537 distance=fabs(GetPixelBlue(image,p)-(double)
1538 GetPixelBlue(reconstruct_image,q));
1539 mean_error_per_pixel+=distance;
1540 mean_error+=distance*distance;
1541 if (distance > maximum_error)
1542 maximum_error=distance;
1544 if (image->matte != MagickFalse)
1546 distance=fabs(GetPixelAlpha(image,p)-(double)
1547 GetPixelAlpha(reconstruct_image,q));
1548 mean_error_per_pixel+=distance;
1549 mean_error+=distance*distance;
1550 if (distance > maximum_error)
1551 maximum_error=distance;
1554 if ((image->colorspace == CMYKColorspace) &&
1555 (reconstruct_image->colorspace == CMYKColorspace))
1557 distance=fabs(GetPixelBlack(image,p)-(double)
1558 GetPixelBlack(reconstruct_image,q));
1559 mean_error_per_pixel+=distance;
1560 mean_error+=distance*distance;
1561 if (distance > maximum_error)
1562 maximum_error=distance;
1565 p+=GetPixelChannels(image);
1566 q+=GetPixelChannels(reconstruct_image);
1569 reconstruct_view=DestroyCacheView(reconstruct_view);
1570 image_view=DestroyCacheView(image_view);
1571 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1572 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1574 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1575 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1580 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1584 % S i m i l a r i t y I m a g e %
1588 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1590 % SimilarityImage() compares the reference image of the image and returns the
1591 % best match offset. In addition, it returns a similarity image such that an
1592 % exact match location is completely white and if none of the pixels match,
1593 % black, otherwise some gray level in-between.
1595 % The format of the SimilarityImageImage method is:
1597 % Image *SimilarityImage(const Image *image,const Image *reference,
1598 % RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
1600 % A description of each parameter follows:
1602 % o image: the image.
1604 % o reference: find an area of the image that closely resembles this image.
1606 % o the best match offset of the reference image within the image.
1608 % o similarity: the computed similarity between the images.
1610 % o exception: return any errors or warnings in this structure.
1614 static double GetNCCDistortion(const Image *image,
1615 const Image *reconstruct_image,
1616 const ChannelStatistics *reconstruct_statistics,ExceptionInfo *exception)
1618 #define SimilarityImageTag "Similarity/Image"
1644 Normalize to account for variation due to lighting and exposure condition.
1646 image_statistics=GetImageStatistics(image,exception);
1649 area=1.0/((MagickRealType) image->columns*image->rows);
1650 image_view=AcquireCacheView(image);
1651 reconstruct_view=AcquireCacheView(reconstruct_image);
1652 for (y=0; y < (ssize_t) image->rows; y++)
1654 register const Quantum
1661 if (status == MagickFalse)
1663 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1664 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1666 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1671 for (x=0; x < (ssize_t) image->columns; x++)
1673 distortion+=area*QuantumScale*(GetPixelRed(image,p)-
1674 image_statistics[RedChannel].mean)*(
1675 GetPixelRed(reconstruct_image,q)-
1676 reconstruct_statistics[RedChannel].mean);
1677 distortion+=area*QuantumScale*(GetPixelGreen(image,p)-
1678 image_statistics[GreenChannel].mean)*(
1679 GetPixelGreen(reconstruct_image,q)-
1680 reconstruct_statistics[GreenChannel].mean);
1681 distortion+=area*QuantumScale*(GetPixelBlue(image,p)-
1682 GetPixelBlue(reconstruct_image,q)-
1683 image_statistics[BlueChannel].mean)*(
1684 reconstruct_statistics[BlueChannel].mean);
1685 if ((image->colorspace == CMYKColorspace) &&
1686 (reconstruct_image->colorspace == CMYKColorspace))
1687 distortion+=area*QuantumScale*(GetPixelBlack(image,p)-
1688 image_statistics[BlackChannel].mean)*(
1689 GetPixelBlack(reconstruct_image,q)-
1690 reconstruct_statistics[BlackChannel].mean);
1691 if (image->matte != MagickFalse)
1692 distortion+=area*QuantumScale*(GetPixelAlpha(image,p)-
1693 image_statistics[OpacityChannel].mean)*(
1694 GetPixelAlpha(reconstruct_image,q)-
1695 reconstruct_statistics[OpacityChannel].mean);
1696 p+=GetPixelChannels(image);
1697 q+=GetPixelChannels(reconstruct_image);
1700 reconstruct_view=DestroyCacheView(reconstruct_view);
1701 image_view=DestroyCacheView(image_view);
1703 Divide by the standard deviation.
1705 gamma=image_statistics[CompositeChannels].standard_deviation*
1706 reconstruct_statistics[CompositeChannels].standard_deviation;
1707 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1708 distortion=QuantumRange*gamma*distortion;
1710 if (image->matte != MagickFalse)
1712 if (image->colorspace == CMYKColorspace)
1714 distortion=sqrt(distortion/number_channels);
1718 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1720 return(1.0-distortion);
1723 static double GetSimilarityMetric(const Image *image,const Image *reference,
1724 const ChannelStatistics *reference_statistics,const ssize_t x_offset,
1725 const ssize_t y_offset,ExceptionInfo *exception)
1736 SetGeometry(reference,&geometry);
1737 geometry.x=x_offset;
1738 geometry.y=y_offset;
1739 similarity_image=CropImage(image,&geometry,exception);
1740 if (similarity_image == (Image *) NULL)
1742 distortion=GetNCCDistortion(reference,similarity_image,reference_statistics,
1744 similarity_image=DestroyImage(similarity_image);
1748 MagickExport Image *SimilarityImage(Image *image,const Image *reference,
1749 RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
1751 #define SimilarityImageTag "Similarity/Image"
1757 *reference_statistics;
1771 assert(image != (const Image *) NULL);
1772 assert(image->signature == MagickSignature);
1773 if (image->debug != MagickFalse)
1774 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1775 assert(exception != (ExceptionInfo *) NULL);
1776 assert(exception->signature == MagickSignature);
1777 assert(offset != (RectangleInfo *) NULL);
1778 SetGeometry(reference,offset);
1779 *similarity_metric=1.0;
1780 if ((reference->columns > image->columns) || (reference->rows > image->rows))
1781 ThrowImageException(ImageError,"ImageSizeDiffers");
1782 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1783 image->rows-reference->rows+1,MagickTrue,exception);
1784 if (similarity_image == (Image *) NULL)
1785 return((Image *) NULL);
1786 if (SetImageStorageClass(similarity_image,DirectClass,exception) == MagickFalse)
1788 similarity_image=DestroyImage(similarity_image);
1789 return((Image *) NULL);
1792 Measure similarity of reference image against image.
1796 reference_statistics=GetImageStatistics(reference,exception);
1797 similarity_view=AcquireCacheView(similarity_image);
1798 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1799 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1801 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
1812 if (status == MagickFalse)
1814 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1816 if (q == (Quantum *) NULL)
1821 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
1823 similarity=GetSimilarityMetric(image,reference,reference_statistics,x,y,
1825 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1826 #pragma omp critical (MagickCore_SimilarityImage)
1828 if (similarity < *similarity_metric)
1830 *similarity_metric=similarity;
1834 SetPixelRed(similarity_image,ClampToQuantum(QuantumRange-
1835 QuantumRange*similarity),q);
1836 SetPixelGreen(similarity_image,GetPixelRed(image,q),q);
1837 SetPixelBlue(similarity_image,GetPixelRed(image,q),q);
1838 q+=GetPixelChannels(similarity_image);
1840 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
1842 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1847 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1848 #pragma omp critical (MagickCore_SimilarityImage)
1850 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1852 if (proceed == MagickFalse)
1856 similarity_view=DestroyCacheView(similarity_view);
1857 reference_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1858 reference_statistics);
1859 return(similarity_image);