2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6 % CCCC OOO M M PPPP AAA RRRR EEEEE %
7 % C O O MM MM P P A A R R E %
8 % C O O M M M PPPP AAAAA RRRR EEE %
9 % C O O M M P A A R R E %
10 % CCCC OOO M M P A A R R EEEEE %
13 % MagickCore Image Comparison Methods %
20 % Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
26 % http://www.imagemagick.org/script/license.php %
28 % Unless required by applicable law or agreed to in writing, software %
29 % distributed under the License is distributed on an "AS IS" BASIS, %
30 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31 % See the License for the specific language governing permissions and %
32 % limitations under the License. %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
43 #include "magick/studio.h"
44 #include "magick/artifact.h"
45 #include "magick/cache-view.h"
46 #include "magick/client.h"
47 #include "magick/color.h"
48 #include "magick/color-private.h"
49 #include "magick/colorspace.h"
50 #include "magick/colorspace-private.h"
51 #include "magick/compare.h"
52 #include "magick/composite-private.h"
53 #include "magick/constitute.h"
54 #include "magick/exception-private.h"
55 #include "magick/geometry.h"
56 #include "magick/image-private.h"
57 #include "magick/list.h"
58 #include "magick/log.h"
59 #include "magick/memory_.h"
60 #include "magick/monitor.h"
61 #include "magick/monitor-private.h"
62 #include "magick/option.h"
63 #include "magick/pixel-private.h"
64 #include "magick/resource_.h"
65 #include "magick/string_.h"
66 #include "magick/utility.h"
67 #include "magick/version.h"
70 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
74 % C o m p a r e I m a g e C h a n n e l s %
78 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
80 % CompareImageChannels() compares one or more image channels of an image
81 % to a reconstructed image and returns the difference image.
83 % The format of the CompareImageChannels method is:
85 % Image *CompareImageChannels(const Image *image,
86 % const Image *reconstruct_image,const ChannelType channel,
87 % const MetricType metric,double *distortion,ExceptionInfo *exception)
89 % A description of each parameter follows:
93 % o reconstruct_image: the reconstruct image.
95 % o channel: the channel.
97 % o metric: the metric.
99 % o distortion: the computed distortion between the images.
101 % o exception: return any errors or warnings in this structure.
105 MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
106 const MetricType metric,double *distortion,ExceptionInfo *exception)
111 highlight_image=CompareImageChannels(image,reconstruct_image,AllChannels,
112 metric,distortion,exception);
113 return(highlight_image);
116 MagickExport Image *CompareImageChannels(Image *image,
117 const Image *reconstruct_image,const ChannelType channel,
118 const MetricType metric,double *distortion,ExceptionInfo *exception)
143 assert(image != (Image *) NULL);
144 assert(image->signature == MagickSignature);
145 if (image->debug != MagickFalse)
146 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
147 assert(reconstruct_image != (const Image *) NULL);
148 assert(reconstruct_image->signature == MagickSignature);
149 assert(distortion != (double *) NULL);
151 if (image->debug != MagickFalse)
152 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
153 if ((reconstruct_image->columns != image->columns) ||
154 (reconstruct_image->rows != image->rows))
155 ThrowImageException(ImageError,"ImageSizeDiffers");
156 status=GetImageChannelDistortion(image,reconstruct_image,channel,metric,
157 distortion,exception);
158 if (status == MagickFalse)
159 return((Image *) NULL);
160 difference_image=CloneImage(image,0,0,MagickTrue,exception);
161 if (difference_image == (Image *) NULL)
162 return((Image *) NULL);
163 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel);
164 highlight_image=CloneImage(image,image->columns,image->rows,MagickTrue,
166 if (highlight_image == (Image *) NULL)
168 difference_image=DestroyImage(difference_image);
169 return((Image *) NULL);
171 if (SetImageStorageClass(highlight_image,DirectClass) == MagickFalse)
173 InheritException(exception,&highlight_image->exception);
174 difference_image=DestroyImage(difference_image);
175 highlight_image=DestroyImage(highlight_image);
176 return((Image *) NULL);
178 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel);
179 (void) QueryMagickColor("#f1001ecc",&highlight,exception);
180 artifact=GetImageArtifact(image,"highlight-color");
181 if (artifact != (const char *) NULL)
182 (void) QueryMagickColor(artifact,&highlight,exception);
183 (void) QueryMagickColor("#ffffffcc",&lowlight,exception);
184 artifact=GetImageArtifact(image,"lowlight-color");
185 if (artifact != (const char *) NULL)
186 (void) QueryMagickColor(artifact,&lowlight,exception);
187 if (highlight_image->colorspace == CMYKColorspace)
189 ConvertRGBToCMYK(&highlight);
190 ConvertRGBToCMYK(&lowlight);
193 Generate difference image.
196 GetMagickPixelPacket(image,&zero);
197 image_view=AcquireCacheView(image);
198 reconstruct_view=AcquireCacheView(reconstruct_image);
199 highlight_view=AcquireCacheView(highlight_image);
200 #if defined(MAGICKCORE_OPENMP_SUPPORT)
201 #pragma omp parallel for schedule(dynamic,4) shared(status)
203 for (y=0; y < (ssize_t) image->rows; y++)
212 register const IndexPacket
214 *restrict reconstruct_indexes;
216 register const PixelPacket
221 *restrict highlight_indexes;
229 if (status == MagickFalse)
231 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
232 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
234 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,highlight_image->columns,
236 if ((p == (const PixelPacket *) NULL) ||
237 (q == (const PixelPacket *) NULL) || (r == (PixelPacket *) NULL))
242 indexes=GetCacheViewVirtualIndexQueue(image_view);
243 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
244 highlight_indexes=GetCacheViewAuthenticIndexQueue(highlight_view);
246 reconstruct_pixel=zero;
247 for (x=0; x < (ssize_t) image->columns; x++)
252 SetMagickPixelPacket(image,p,indexes+x,&pixel);
253 SetMagickPixelPacket(reconstruct_image,q,reconstruct_indexes+x,
255 difference=MagickFalse;
256 if (channel == AllChannels)
258 if (IsMagickColorSimilar(&pixel,&reconstruct_pixel) == MagickFalse)
259 difference=MagickTrue;
263 if (((channel & RedChannel) != 0) && (p->red != q->red))
264 difference=MagickTrue;
265 if (((channel & GreenChannel) != 0) && (p->green != q->green))
266 difference=MagickTrue;
267 if (((channel & BlueChannel) != 0) && (p->blue != q->blue))
268 difference=MagickTrue;
269 if (((channel & OpacityChannel) != 0) &&
270 (image->matte != MagickFalse) && (p->opacity != q->opacity))
271 difference=MagickTrue;
272 if ((((channel & IndexChannel) != 0) &&
273 (image->colorspace == CMYKColorspace) &&
274 (reconstruct_image->colorspace == CMYKColorspace)) &&
275 (indexes[x] != reconstruct_indexes[x]))
276 difference=MagickTrue;
278 if (difference != MagickFalse)
279 SetPixelPacket(highlight_image,&highlight,r,highlight_indexes+x);
281 SetPixelPacket(highlight_image,&lowlight,r,highlight_indexes+x);
286 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
287 if (sync == MagickFalse)
290 highlight_view=DestroyCacheView(highlight_view);
291 reconstruct_view=DestroyCacheView(reconstruct_view);
292 image_view=DestroyCacheView(image_view);
293 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0);
294 highlight_image=DestroyImage(highlight_image);
295 if (status == MagickFalse)
296 difference_image=DestroyImage(difference_image);
297 return(difference_image);
301 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
305 % 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 %
309 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
311 % GetImageChannelDistortion() compares one or more image channels of an image
312 % to a reconstructed image and returns the specified distortion metric.
314 % The format of the CompareImageChannels method is:
316 % MagickBooleanType GetImageChannelDistortion(const Image *image,
317 % const Image *reconstruct_image,const ChannelType channel,
318 % const MetricType metric,double *distortion,ExceptionInfo *exception)
320 % A description of each parameter follows:
322 % o image: the image.
324 % o reconstruct_image: the reconstruct image.
326 % o channel: the channel.
328 % o metric: the metric.
330 % o distortion: the computed distortion between the images.
332 % o exception: return any errors or warnings in this structure.
336 MagickExport MagickBooleanType GetImageDistortion(Image *image,
337 const Image *reconstruct_image,const MetricType metric,double *distortion,
338 ExceptionInfo *exception)
343 status=GetImageChannelDistortion(image,reconstruct_image,AllChannels,
344 metric,distortion,exception);
348 static MagickBooleanType GetAbsoluteError(const Image *image,
349 const Image *reconstruct_image,const ChannelType channel,double *distortion,
350 ExceptionInfo *exception)
366 Compute the absolute difference in pixels between two images.
369 GetMagickPixelPacket(image,&zero);
370 image_view=AcquireCacheView(image);
371 reconstruct_view=AcquireCacheView(reconstruct_image);
372 #if defined(MAGICKCORE_OPENMP_SUPPORT)
373 #pragma omp parallel for schedule(dynamic,4) shared(status)
375 for (y=0; y < (ssize_t) image->rows; y++)
378 channel_distortion[AllChannels+1];
384 register const IndexPacket
386 *restrict reconstruct_indexes;
388 register const PixelPacket
396 if (status == MagickFalse)
398 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
399 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
401 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
406 indexes=GetCacheViewVirtualIndexQueue(image_view);
407 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
409 reconstruct_pixel=pixel;
410 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
411 for (x=0; x < (ssize_t) image->columns; x++)
413 SetMagickPixelPacket(image,p,indexes+x,&pixel);
414 SetMagickPixelPacket(reconstruct_image,q,reconstruct_indexes+x,
416 if (IsMagickColorSimilar(&pixel,&reconstruct_pixel) == MagickFalse)
418 if ((channel & RedChannel) != 0)
419 channel_distortion[RedChannel]++;
420 if ((channel & GreenChannel) != 0)
421 channel_distortion[GreenChannel]++;
422 if ((channel & BlueChannel) != 0)
423 channel_distortion[BlueChannel]++;
424 if (((channel & OpacityChannel) != 0) &&
425 (image->matte != MagickFalse))
426 channel_distortion[OpacityChannel]++;
427 if (((channel & IndexChannel) != 0) &&
428 (image->colorspace == CMYKColorspace))
429 channel_distortion[BlackChannel]++;
430 channel_distortion[AllChannels]++;
435 #if defined(MAGICKCORE_OPENMP_SUPPORT)
436 #pragma omp critical (MagickCore_GetAbsoluteError)
438 for (i=0; i <= (ssize_t) AllChannels; i++)
439 distortion[i]+=channel_distortion[i];
441 reconstruct_view=DestroyCacheView(reconstruct_view);
442 image_view=DestroyCacheView(image_view);
446 static size_t GetNumberChannels(const Image *image,
447 const ChannelType channel)
453 if ((channel & RedChannel) != 0)
455 if ((channel & GreenChannel) != 0)
457 if ((channel & BlueChannel) != 0)
459 if (((channel & OpacityChannel) != 0) &&
460 (image->matte != MagickFalse))
462 if (((channel & IndexChannel) != 0) &&
463 (image->colorspace == CMYKColorspace))
468 static MagickBooleanType GetMeanAbsoluteError(const Image *image,
469 const Image *reconstruct_image,const ChannelType channel,
470 double *distortion,ExceptionInfo *exception)
486 image_view=AcquireCacheView(image);
487 reconstruct_view=AcquireCacheView(reconstruct_image);
488 #if defined(MAGICKCORE_OPENMP_SUPPORT)
489 #pragma omp parallel for schedule(dynamic,4) shared(status)
491 for (y=0; y < (ssize_t) image->rows; y++)
494 channel_distortion[AllChannels+1];
496 register const IndexPacket
498 *restrict reconstruct_indexes;
500 register const PixelPacket
508 if (status == MagickFalse)
510 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
511 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,
512 reconstruct_image->columns,1,exception);
513 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
518 indexes=GetCacheViewVirtualIndexQueue(image_view);
519 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
520 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
521 for (x=0; x < (ssize_t) image->columns; x++)
526 if ((channel & RedChannel) != 0)
528 distance=QuantumScale*fabs(p->red-(double) q->red);
529 channel_distortion[RedChannel]+=distance;
530 channel_distortion[AllChannels]+=distance;
532 if ((channel & GreenChannel) != 0)
534 distance=QuantumScale*fabs(p->green-(double) q->green);
535 channel_distortion[GreenChannel]+=distance;
536 channel_distortion[AllChannels]+=distance;
538 if ((channel & BlueChannel) != 0)
540 distance=QuantumScale*fabs(p->blue-(double) q->blue);
541 channel_distortion[BlueChannel]+=distance;
542 channel_distortion[AllChannels]+=distance;
544 if (((channel & OpacityChannel) != 0) &&
545 (image->matte != MagickFalse))
547 distance=QuantumScale*fabs(p->opacity-(double) q->opacity);
548 channel_distortion[OpacityChannel]+=distance;
549 channel_distortion[AllChannels]+=distance;
551 if (((channel & IndexChannel) != 0) &&
552 (image->colorspace == CMYKColorspace))
554 distance=QuantumScale*fabs(indexes[x]-(double)
555 reconstruct_indexes[x]);
556 channel_distortion[BlackChannel]+=distance;
557 channel_distortion[AllChannels]+=distance;
562 #if defined(MAGICKCORE_OPENMP_SUPPORT)
563 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
565 for (i=0; i <= (ssize_t) AllChannels; i++)
566 distortion[i]+=channel_distortion[i];
568 reconstruct_view=DestroyCacheView(reconstruct_view);
569 image_view=DestroyCacheView(image_view);
570 for (i=0; i <= (ssize_t) AllChannels; i++)
571 distortion[i]/=((double) image->columns*image->rows);
572 distortion[AllChannels]/=(double) GetNumberChannels(image,channel);
576 static MagickBooleanType GetMeanErrorPerPixel(Image *image,
577 const Image *reconstruct_image,const ChannelType channel,double *distortion,
578 ExceptionInfo *exception)
603 image_view=AcquireCacheView(image);
604 reconstruct_view=AcquireCacheView(reconstruct_image);
605 for (y=0; y < (ssize_t) image->rows; y++)
607 register const IndexPacket
609 *restrict reconstruct_indexes;
611 register const PixelPacket
618 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
619 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
621 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
626 indexes=GetCacheViewVirtualIndexQueue(image_view);
627 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
628 for (x=0; x < (ssize_t) image->columns; x++)
633 if ((channel & OpacityChannel) != 0)
635 if (image->matte != MagickFalse)
636 alpha=(MagickRealType) (QuantumScale*(GetAlphaPixelComponent(p)));
637 if (reconstruct_image->matte != MagickFalse)
638 beta=(MagickRealType) (QuantumScale*GetAlphaPixelComponent(q));
640 if ((channel & RedChannel) != 0)
642 distance=fabs(alpha*p->red-beta*q->red);
643 distortion[RedChannel]+=distance;
644 distortion[AllChannels]+=distance;
645 mean_error+=distance*distance;
646 if (distance > maximum_error)
647 maximum_error=distance;
650 if ((channel & GreenChannel) != 0)
652 distance=fabs(alpha*p->green-beta*q->green);
653 distortion[GreenChannel]+=distance;
654 distortion[AllChannels]+=distance;
655 mean_error+=distance*distance;
656 if (distance > maximum_error)
657 maximum_error=distance;
660 if ((channel & BlueChannel) != 0)
662 distance=fabs(alpha*p->blue-beta*q->blue);
663 distortion[BlueChannel]+=distance;
664 distortion[AllChannels]+=distance;
665 mean_error+=distance*distance;
666 if (distance > maximum_error)
667 maximum_error=distance;
670 if (((channel & OpacityChannel) != 0) &&
671 (image->matte != MagickFalse))
673 distance=fabs((double) p->opacity-q->opacity);
674 distortion[OpacityChannel]+=distance;
675 distortion[AllChannels]+=distance;
676 mean_error+=distance*distance;
677 if (distance > maximum_error)
678 maximum_error=distance;
681 if (((channel & IndexChannel) != 0) &&
682 (image->colorspace == CMYKColorspace) &&
683 (reconstruct_image->colorspace == CMYKColorspace))
685 distance=fabs(alpha*indexes[x]-beta*reconstruct_indexes[x]);
686 distortion[BlackChannel]+=distance;
687 distortion[AllChannels]+=distance;
688 mean_error+=distance*distance;
689 if (distance > maximum_error)
690 maximum_error=distance;
697 reconstruct_view=DestroyCacheView(reconstruct_view);
698 image_view=DestroyCacheView(image_view);
699 image->error.mean_error_per_pixel=distortion[AllChannels]/area;
700 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
701 image->error.normalized_maximum_error=QuantumScale*maximum_error;
705 static MagickBooleanType GetMeanSquaredError(const Image *image,
706 const Image *reconstruct_image,const ChannelType channel,
707 double *distortion,ExceptionInfo *exception)
723 image_view=AcquireCacheView(image);
724 reconstruct_view=AcquireCacheView(reconstruct_image);
725 #if defined(MAGICKCORE_OPENMP_SUPPORT)
726 #pragma omp parallel for schedule(dynamic,4) shared(status)
728 for (y=0; y < (ssize_t) image->rows; y++)
731 channel_distortion[AllChannels+1];
733 register const IndexPacket
735 *restrict reconstruct_indexes;
737 register const PixelPacket
745 if (status == MagickFalse)
747 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
748 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,
749 reconstruct_image->columns,1,exception);
750 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
755 indexes=GetCacheViewVirtualIndexQueue(image_view);
756 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
757 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
758 for (x=0; x < (ssize_t) image->columns; x++)
763 if ((channel & RedChannel) != 0)
765 distance=QuantumScale*(p->red-(MagickRealType) q->red);
766 channel_distortion[RedChannel]+=distance*distance;
767 channel_distortion[AllChannels]+=distance*distance;
769 if ((channel & GreenChannel) != 0)
771 distance=QuantumScale*(p->green-(MagickRealType) q->green);
772 channel_distortion[GreenChannel]+=distance*distance;
773 channel_distortion[AllChannels]+=distance*distance;
775 if ((channel & BlueChannel) != 0)
777 distance=QuantumScale*(p->blue-(MagickRealType) q->blue);
778 channel_distortion[BlueChannel]+=distance*distance;
779 channel_distortion[AllChannels]+=distance*distance;
781 if (((channel & OpacityChannel) != 0) &&
782 (image->matte != MagickFalse))
784 distance=QuantumScale*(p->opacity-(MagickRealType) q->opacity);
785 channel_distortion[OpacityChannel]+=distance*distance;
786 channel_distortion[AllChannels]+=distance*distance;
788 if (((channel & IndexChannel) != 0) &&
789 (image->colorspace == CMYKColorspace) &&
790 (reconstruct_image->colorspace == CMYKColorspace))
792 distance=QuantumScale*(indexes[x]-(MagickRealType)
793 reconstruct_indexes[x]);
794 channel_distortion[BlackChannel]+=distance*distance;
795 channel_distortion[AllChannels]+=distance*distance;
800 #if defined(MAGICKCORE_OPENMP_SUPPORT)
801 #pragma omp critical (MagickCore_GetMeanSquaredError)
803 for (i=0; i <= (ssize_t) AllChannels; i++)
804 distortion[i]+=channel_distortion[i];
806 reconstruct_view=DestroyCacheView(reconstruct_view);
807 image_view=DestroyCacheView(image_view);
808 for (i=0; i <= (ssize_t) AllChannels; i++)
809 distortion[i]/=((double) image->columns*image->rows);
810 distortion[AllChannels]/=(double) GetNumberChannels(image,channel);
814 static MagickBooleanType GetNormalizedCrossCorrelationError(const Image *image,
815 const Image *reconstruct_image,const ChannelType channel,
816 double *distortion,ExceptionInfo *exception)
832 image_view=AcquireCacheView(image);
833 reconstruct_view=AcquireCacheView(reconstruct_image);
834 #if defined(MAGICKCORE_OPENMP_SUPPORT)
835 #pragma omp parallel for schedule(dynamic,4) shared(status)
837 for (y=0; y < (ssize_t) image->rows; y++)
840 channel_distortion[AllChannels+1];
842 register const IndexPacket
844 *restrict reconstruct_indexes;
846 register const PixelPacket
854 if (status == MagickFalse)
856 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
857 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,
858 reconstruct_image->columns,1,exception);
859 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
864 indexes=GetCacheViewVirtualIndexQueue(image_view);
865 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
866 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
867 for (x=0; x < (ssize_t) image->columns; x++)
872 if ((channel & RedChannel) != 0)
874 distance=QuantumScale*fabs(p->red-(double) q->red);
875 channel_distortion[RedChannel]+=distance;
876 channel_distortion[AllChannels]+=distance;
878 if ((channel & GreenChannel) != 0)
880 distance=QuantumScale*fabs(p->green-(double) q->green);
881 channel_distortion[GreenChannel]+=distance;
882 channel_distortion[AllChannels]+=distance;
884 if ((channel & BlueChannel) != 0)
886 distance=QuantumScale*fabs(p->blue-(double) q->blue);
887 channel_distortion[BlueChannel]+=distance;
888 channel_distortion[AllChannels]+=distance;
890 if (((channel & OpacityChannel) != 0) &&
891 (image->matte != MagickFalse))
893 distance=QuantumScale*fabs(p->opacity-(double) q->opacity);
894 channel_distortion[OpacityChannel]+=distance;
895 channel_distortion[AllChannels]+=distance;
897 if (((channel & IndexChannel) != 0) &&
898 (image->colorspace == CMYKColorspace))
900 distance=QuantumScale*fabs(indexes[x]-(double)
901 reconstruct_indexes[x]);
902 channel_distortion[BlackChannel]+=distance;
903 channel_distortion[AllChannels]+=distance;
908 #if defined(MAGICKCORE_OPENMP_SUPPORT)
909 #pragma omp critical (MagickCore_GetNormalizedCrossCorrelationError)
911 for (i=0; i <= (ssize_t) AllChannels; i++)
912 distortion[i]+=channel_distortion[i];
914 reconstruct_view=DestroyCacheView(reconstruct_view);
915 image_view=DestroyCacheView(image_view);
916 for (i=0; i <= (ssize_t) AllChannels; i++)
917 distortion[i]/=((double) image->columns*image->rows);
918 distortion[AllChannels]/=(double) GetNumberChannels(image,channel);
922 static MagickBooleanType GetPeakAbsoluteError(const Image *image,
923 const Image *reconstruct_image,const ChannelType channel,
924 double *distortion,ExceptionInfo *exception)
937 image_view=AcquireCacheView(image);
938 reconstruct_view=AcquireCacheView(reconstruct_image);
939 #if defined(MAGICKCORE_OPENMP_SUPPORT)
940 #pragma omp parallel for schedule(dynamic,4) shared(status)
942 for (y=0; y < (ssize_t) image->rows; y++)
945 channel_distortion[AllChannels+1];
947 register const IndexPacket
949 *restrict reconstruct_indexes;
951 register const PixelPacket
959 if (status == MagickFalse)
961 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
962 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,
963 reconstruct_image->columns,1,exception);
964 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
969 indexes=GetCacheViewVirtualIndexQueue(image_view);
970 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
971 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
972 for (x=0; x < (ssize_t) image->columns; x++)
977 if ((channel & RedChannel) != 0)
979 distance=QuantumScale*fabs(p->red-(double) q->red);
980 if (distance > channel_distortion[RedChannel])
981 channel_distortion[RedChannel]=distance;
982 if (distance > channel_distortion[AllChannels])
983 channel_distortion[AllChannels]=distance;
985 if ((channel & GreenChannel) != 0)
987 distance=QuantumScale*fabs(p->green-(double) q->green);
988 if (distance > channel_distortion[GreenChannel])
989 channel_distortion[GreenChannel]=distance;
990 if (distance > channel_distortion[AllChannels])
991 channel_distortion[AllChannels]=distance;
993 if ((channel & BlueChannel) != 0)
995 distance=QuantumScale*fabs(p->blue-(double) q->blue);
996 if (distance > channel_distortion[BlueChannel])
997 channel_distortion[BlueChannel]=distance;
998 if (distance > channel_distortion[AllChannels])
999 channel_distortion[AllChannels]=distance;
1001 if (((channel & OpacityChannel) != 0) &&
1002 (image->matte != MagickFalse))
1004 distance=QuantumScale*fabs(p->opacity-(double) q->opacity);
1005 if (distance > channel_distortion[OpacityChannel])
1006 channel_distortion[OpacityChannel]=distance;
1007 if (distance > channel_distortion[AllChannels])
1008 channel_distortion[AllChannels]=distance;
1010 if (((channel & IndexChannel) != 0) &&
1011 (image->colorspace == CMYKColorspace) &&
1012 (reconstruct_image->colorspace == CMYKColorspace))
1014 distance=QuantumScale*fabs(indexes[x]-(double)
1015 reconstruct_indexes[x]);
1016 if (distance > channel_distortion[BlackChannel])
1017 channel_distortion[BlackChannel]=distance;
1018 if (distance > channel_distortion[AllChannels])
1019 channel_distortion[AllChannels]=distance;
1024 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1025 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1027 for (i=0; i <= (ssize_t) AllChannels; i++)
1028 if (channel_distortion[i] > distortion[i])
1029 distortion[i]=channel_distortion[i];
1031 reconstruct_view=DestroyCacheView(reconstruct_view);
1032 image_view=DestroyCacheView(image_view);
1036 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
1037 const Image *reconstruct_image,const ChannelType channel,
1038 double *distortion,ExceptionInfo *exception)
1043 status=GetMeanSquaredError(image,reconstruct_image,channel,distortion,
1045 if ((channel & RedChannel) != 0)
1046 distortion[RedChannel]=20.0*log10((double) 1.0/sqrt(
1047 distortion[RedChannel]));
1048 if ((channel & GreenChannel) != 0)
1049 distortion[GreenChannel]=20.0*log10((double) 1.0/sqrt(
1050 distortion[GreenChannel]));
1051 if ((channel & BlueChannel) != 0)
1052 distortion[BlueChannel]=20.0*log10((double) 1.0/sqrt(
1053 distortion[BlueChannel]));
1054 if (((channel & OpacityChannel) != 0) &&
1055 (image->matte != MagickFalse))
1056 distortion[OpacityChannel]=20.0*log10((double) 1.0/sqrt(
1057 distortion[OpacityChannel]));
1058 if (((channel & IndexChannel) != 0) &&
1059 (image->colorspace == CMYKColorspace))
1060 distortion[BlackChannel]=20.0*log10((double) 1.0/sqrt(
1061 distortion[BlackChannel]));
1062 distortion[AllChannels]=20.0*log10((double) 1.0/sqrt(
1063 distortion[AllChannels]));
1067 static MagickBooleanType GetRootMeanSquaredError(const Image *image,
1068 const Image *reconstruct_image,const ChannelType channel,
1069 double *distortion,ExceptionInfo *exception)
1074 status=GetMeanSquaredError(image,reconstruct_image,channel,distortion,
1076 if ((channel & RedChannel) != 0)
1077 distortion[RedChannel]=sqrt(distortion[RedChannel]);
1078 if ((channel & GreenChannel) != 0)
1079 distortion[GreenChannel]=sqrt(distortion[GreenChannel]);
1080 if ((channel & BlueChannel) != 0)
1081 distortion[BlueChannel]=sqrt(distortion[BlueChannel]);
1082 if (((channel & OpacityChannel) != 0) &&
1083 (image->matte != MagickFalse))
1084 distortion[OpacityChannel]=sqrt(distortion[OpacityChannel]);
1085 if (((channel & IndexChannel) != 0) &&
1086 (image->colorspace == CMYKColorspace))
1087 distortion[BlackChannel]=sqrt(distortion[BlackChannel]);
1088 distortion[AllChannels]=sqrt(distortion[AllChannels]);
1092 MagickExport MagickBooleanType GetImageChannelDistortion(Image *image,
1093 const Image *reconstruct_image,const ChannelType channel,
1094 const MetricType metric,double *distortion,ExceptionInfo *exception)
1097 *channel_distortion;
1105 assert(image != (Image *) NULL);
1106 assert(image->signature == MagickSignature);
1107 if (image->debug != MagickFalse)
1108 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1109 assert(reconstruct_image != (const Image *) NULL);
1110 assert(reconstruct_image->signature == MagickSignature);
1111 assert(distortion != (double *) NULL);
1113 if (image->debug != MagickFalse)
1114 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1115 if ((reconstruct_image->columns != image->columns) ||
1116 (reconstruct_image->rows != image->rows))
1117 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1119 Get image distortion.
1121 length=AllChannels+1UL;
1122 channel_distortion=(double *) AcquireQuantumMemory(length,
1123 sizeof(*channel_distortion));
1124 if (channel_distortion == (double *) NULL)
1125 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1126 (void) ResetMagickMemory(channel_distortion,0,length*
1127 sizeof(*channel_distortion));
1130 case AbsoluteErrorMetric:
1132 status=GetAbsoluteError(image,reconstruct_image,channel,
1133 channel_distortion,exception);
1136 case MeanAbsoluteErrorMetric:
1138 status=GetMeanAbsoluteError(image,reconstruct_image,channel,
1139 channel_distortion,exception);
1142 case MeanErrorPerPixelMetric:
1144 status=GetMeanErrorPerPixel(image,reconstruct_image,channel,
1145 channel_distortion,exception);
1148 case MeanSquaredErrorMetric:
1150 status=GetMeanSquaredError(image,reconstruct_image,channel,
1151 channel_distortion,exception);
1154 case NormalizedCrossCorrelationErrorMetric:
1156 status=GetNormalizedCrossCorrelationError(image,reconstruct_image,channel,
1157 channel_distortion,exception);
1160 case PeakAbsoluteErrorMetric:
1163 status=GetPeakAbsoluteError(image,reconstruct_image,channel,
1164 channel_distortion,exception);
1167 case PeakSignalToNoiseRatioMetric:
1169 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,channel,
1170 channel_distortion,exception);
1173 case RootMeanSquaredErrorMetric:
1175 status=GetRootMeanSquaredError(image,reconstruct_image,channel,
1176 channel_distortion,exception);
1180 *distortion=channel_distortion[AllChannels];
1181 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1186 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1190 % 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 %
1194 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1196 % GetImageChannelDistrortion() compares the image channels of an image to a
1197 % reconstructed image and returns the specified distortion metric for each
1200 % The format of the CompareImageChannels method is:
1202 % double *GetImageChannelDistortions(const Image *image,
1203 % const Image *reconstruct_image,const MetricType metric,
1204 % ExceptionInfo *exception)
1206 % A description of each parameter follows:
1208 % o image: the image.
1210 % o reconstruct_image: the reconstruct image.
1212 % o metric: the metric.
1214 % o exception: return any errors or warnings in this structure.
1217 MagickExport double *GetImageChannelDistortions(Image *image,
1218 const Image *reconstruct_image,const MetricType metric,
1219 ExceptionInfo *exception)
1222 *channel_distortion;
1230 assert(image != (Image *) NULL);
1231 assert(image->signature == MagickSignature);
1232 if (image->debug != MagickFalse)
1233 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1234 assert(reconstruct_image != (const Image *) NULL);
1235 assert(reconstruct_image->signature == MagickSignature);
1236 if (image->debug != MagickFalse)
1237 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1238 if ((reconstruct_image->columns != image->columns) ||
1239 (reconstruct_image->rows != image->rows))
1241 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1242 ImageError,"ImageSizeDiffers","`%s'",image->filename);
1243 return((double *) NULL);
1246 Get image distortion.
1248 length=AllChannels+1UL;
1249 channel_distortion=(double *) AcquireQuantumMemory(length,
1250 sizeof(*channel_distortion));
1251 if (channel_distortion == (double *) NULL)
1252 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1253 (void) ResetMagickMemory(channel_distortion,0,length*
1254 sizeof(*channel_distortion));
1257 case AbsoluteErrorMetric:
1259 status=GetAbsoluteError(image,reconstruct_image,AllChannels,
1260 channel_distortion,exception);
1263 case MeanAbsoluteErrorMetric:
1265 status=GetMeanAbsoluteError(image,reconstruct_image,AllChannels,
1266 channel_distortion,exception);
1269 case MeanErrorPerPixelMetric:
1271 status=GetMeanErrorPerPixel(image,reconstruct_image,AllChannels,
1272 channel_distortion,exception);
1275 case MeanSquaredErrorMetric:
1277 status=GetMeanSquaredError(image,reconstruct_image,AllChannels,
1278 channel_distortion,exception);
1281 case NormalizedCrossCorrelationErrorMetric:
1283 status=GetNormalizedCrossCorrelationError(image,reconstruct_image,
1284 AllChannels,channel_distortion,exception);
1287 case PeakAbsoluteErrorMetric:
1290 status=GetPeakAbsoluteError(image,reconstruct_image,AllChannels,
1291 channel_distortion,exception);
1294 case PeakSignalToNoiseRatioMetric:
1296 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,AllChannels,
1297 channel_distortion,exception);
1300 case RootMeanSquaredErrorMetric:
1302 status=GetRootMeanSquaredError(image,reconstruct_image,AllChannels,
1303 channel_distortion,exception);
1307 return(channel_distortion);
1311 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1315 % I s I m a g e s E q u a l %
1319 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1321 % IsImagesEqual() measures the difference between colors at each pixel
1322 % location of two images. A value other than 0 means the colors match
1323 % exactly. Otherwise an error measure is computed by summing over all
1324 % pixels in an image the distance squared in RGB space between each image
1325 % pixel and its corresponding pixel in the reconstruct image. The error
1326 % measure is assigned to these image members:
1328 % o mean_error_per_pixel: The mean error for any single pixel in
1331 % o normalized_mean_error: The normalized mean quantization error for
1332 % any single pixel in the image. This distance measure is normalized to
1333 % a range between 0 and 1. It is independent of the range of red, green,
1334 % and blue values in the image.
1336 % o normalized_maximum_error: The normalized maximum quantization
1337 % error for any single pixel in the image. This distance measure is
1338 % normalized to a range between 0 and 1. It is independent of the range
1339 % of red, green, and blue values in your image.
1341 % A small normalized mean square error, accessed as
1342 % image->normalized_mean_error, suggests the images are very similar in
1343 % spatial layout and color.
1345 % The format of the IsImagesEqual method is:
1347 % MagickBooleanType IsImagesEqual(Image *image,
1348 % const Image *reconstruct_image)
1350 % A description of each parameter follows.
1352 % o image: the image.
1354 % o reconstruct_image: the reconstruct image.
1357 MagickExport MagickBooleanType IsImagesEqual(Image *image,
1358 const Image *reconstruct_image)
1377 mean_error_per_pixel;
1379 assert(image != (Image *) NULL);
1380 assert(image->signature == MagickSignature);
1381 assert(reconstruct_image != (const Image *) NULL);
1382 assert(reconstruct_image->signature == MagickSignature);
1383 if ((reconstruct_image->columns != image->columns) ||
1384 (reconstruct_image->rows != image->rows))
1385 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1388 mean_error_per_pixel=0.0;
1390 exception=(&image->exception);
1391 image_view=AcquireCacheView(image);
1392 reconstruct_view=AcquireCacheView(reconstruct_image);
1393 for (y=0; y < (ssize_t) image->rows; y++)
1395 register const IndexPacket
1397 *restrict reconstruct_indexes;
1399 register const PixelPacket
1406 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1407 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1409 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
1411 indexes=GetCacheViewVirtualIndexQueue(image_view);
1412 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1413 for (x=0; x < (ssize_t) image->columns; x++)
1418 distance=fabs(p->red-(double) q->red);
1419 mean_error_per_pixel+=distance;
1420 mean_error+=distance*distance;
1421 if (distance > maximum_error)
1422 maximum_error=distance;
1424 distance=fabs(p->green-(double) q->green);
1425 mean_error_per_pixel+=distance;
1426 mean_error+=distance*distance;
1427 if (distance > maximum_error)
1428 maximum_error=distance;
1430 distance=fabs(p->blue-(double) q->blue);
1431 mean_error_per_pixel+=distance;
1432 mean_error+=distance*distance;
1433 if (distance > maximum_error)
1434 maximum_error=distance;
1436 if (image->matte != MagickFalse)
1438 distance=fabs(p->opacity-(double) q->opacity);
1439 mean_error_per_pixel+=distance;
1440 mean_error+=distance*distance;
1441 if (distance > maximum_error)
1442 maximum_error=distance;
1445 if ((image->colorspace == CMYKColorspace) &&
1446 (reconstruct_image->colorspace == CMYKColorspace))
1448 distance=fabs(indexes[x]-(double) reconstruct_indexes[x]);
1449 mean_error_per_pixel+=distance;
1450 mean_error+=distance*distance;
1451 if (distance > maximum_error)
1452 maximum_error=distance;
1459 reconstruct_view=DestroyCacheView(reconstruct_view);
1460 image_view=DestroyCacheView(image_view);
1461 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1462 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1464 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1465 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1470 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1474 % S i m i l a r i t y I m a g e %
1478 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1480 % SimilarityImage() compares the reference image of the image and returns the
1481 % best match offset. In addition, it returns a similarity image such that an
1482 % exact match location is completely white and if none of the pixels match,
1483 % black, otherwise some gray level in-between.
1485 % The format of the SimilarityImageImage method is:
1487 % Image *SimilarityImage(const Image *image,const Image *reference,
1488 % RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
1490 % A description of each parameter follows:
1492 % o image: the image.
1494 % o reference: find an area of the image that closely resembles this image.
1496 % o the best match offset of the reference image within the image.
1498 % o similarity: the computed similarity between the images.
1500 % o exception: return any errors or warnings in this structure.
1504 static double GetSimilarityMetric(const Image *image,const Image *reference,
1505 const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception)
1521 Compute the similarity in pixels between two images.
1525 image_view=AcquireCacheView(image);
1526 reference_view=AcquireCacheView(reference);
1527 for (y=0; y < (ssize_t) reference->rows; y++)
1529 register const IndexPacket
1531 *restrict reference_indexes;
1533 register const PixelPacket
1540 if (status == MagickFalse)
1542 p=GetCacheViewVirtualPixels(image_view,x_offset,y_offset+y,
1543 reference->columns,1,exception);
1544 q=GetCacheViewVirtualPixels(reference_view,0,y,reference->columns,1,
1546 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
1551 indexes=GetCacheViewVirtualIndexQueue(image_view);
1552 reference_indexes=GetCacheViewVirtualIndexQueue(reference_view);
1553 for (x=0; x < (ssize_t) reference->columns; x++)
1561 thread_similarity=0.0;
1562 distance=QuantumScale*(p->red-(MagickRealType) q->red);
1563 thread_similarity+=distance*distance;
1564 distance=QuantumScale*(p->green-(MagickRealType) q->green);
1565 thread_similarity+=distance*distance;
1566 distance=QuantumScale*(p->blue-(MagickRealType) q->blue);
1567 thread_similarity+=distance*distance;
1568 if ((image->matte != MagickFalse) && (reference->matte != MagickFalse))
1570 distance=QuantumScale*(p->opacity-(MagickRealType) q->opacity);
1571 thread_similarity+=distance*distance;
1573 if ((image->colorspace == CMYKColorspace) &&
1574 (reference->colorspace == CMYKColorspace))
1576 distance=QuantumScale*(indexes[x]-(MagickRealType)
1577 reference_indexes[x]);
1578 thread_similarity+=distance*distance;
1580 similarity+=thread_similarity;
1585 reference_view=DestroyCacheView(reference_view);
1586 image_view=DestroyCacheView(image_view);
1587 if (status == MagickFalse)
1589 similarity/=((double) reference->columns*reference->rows);
1590 similarity/=(double) GetNumberChannels(reference,AllChannels);
1591 return(sqrt(similarity));
1594 MagickExport Image *SimilarityImage(Image *image,const Image *reference,
1595 RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
1597 #define SimilarityImageTag "Similarity/Image"
1614 assert(image != (const Image *) NULL);
1615 assert(image->signature == MagickSignature);
1616 if (image->debug != MagickFalse)
1617 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1618 assert(exception != (ExceptionInfo *) NULL);
1619 assert(exception->signature == MagickSignature);
1620 assert(offset != (RectangleInfo *) NULL);
1621 SetGeometry(reference,offset);
1622 *similarity_metric=1.0;
1623 if ((reference->columns > image->columns) || (reference->rows > image->rows))
1624 ThrowImageException(ImageError,"ImageSizeDiffers");
1625 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1626 image->rows-reference->rows+1,MagickTrue,exception);
1627 if (similarity_image == (Image *) NULL)
1628 return((Image *) NULL);
1629 if (SetImageStorageClass(similarity_image,DirectClass) == MagickFalse)
1631 InheritException(exception,&similarity_image->exception);
1632 similarity_image=DestroyImage(similarity_image);
1633 return((Image *) NULL);
1636 Measure similarity of reference image against image.
1640 similarity_view=AcquireCacheView(similarity_image);
1641 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1642 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1644 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
1652 register PixelPacket
1655 if (status == MagickFalse)
1657 q=GetCacheViewAuthenticPixels(similarity_view,0,y,
1658 similarity_image->columns,1,exception);
1659 if (q == (const PixelPacket *) NULL)
1664 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
1666 similarity=GetSimilarityMetric(image,reference,x,y,exception);
1667 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1668 #pragma omp critical (MagickCore_SimilarityImage)
1670 if (similarity < *similarity_metric)
1672 *similarity_metric=similarity;
1676 q->red=ClampToQuantum(QuantumRange-QuantumRange*similarity);
1681 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
1683 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1688 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1689 #pragma omp critical (MagickCore_SimilarityImage)
1691 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1693 if (proceed == MagickFalse)
1697 similarity_view=DestroyCacheView(similarity_view);
1698 return(similarity_image);