MagickCore  6.7.5
compare.c
Go to the documentation of this file.
00001 /*
00002 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00003 %                                                                             %
00004 %                                                                             %
00005 %                                                                             %
00006 %               CCCC   OOO   M   M  PPPP    AAA   RRRR    EEEEE               %
00007 %              C      O   O  MM MM  P   P  A   A  R   R   E                   %
00008 %              C      O   O  M M M  PPPP   AAAAA  RRRR    EEE                 %
00009 %              C      O   O  M   M  P      A   A  R R     E                   %
00010 %               CCCC   OOO   M   M  P      A   A  R  R    EEEEE               %
00011 %                                                                             %
00012 %                                                                             %
00013 %                      MagickCore Image Comparison Methods                    %
00014 %                                                                             %
00015 %                              Software Design                                %
00016 %                                John Cristy                                  %
00017 %                               December 2003                                 %
00018 %                                                                             %
00019 %                                                                             %
00020 %  Copyright 1999-2012 ImageMagick Studio LLC, a non-profit organization      %
00021 %  dedicated to making software imaging solutions freely available.           %
00022 %                                                                             %
00023 %  You may not use this file except in compliance with the License.  You may  %
00024 %  obtain a copy of the License at                                            %
00025 %                                                                             %
00026 %    http://www.imagemagick.org/script/license.php                            %
00027 %                                                                             %
00028 %  Unless required by applicable law or agreed to in writing, software        %
00029 %  distributed under the License is distributed on an "AS IS" BASIS,          %
00030 %  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   %
00031 %  See the License for the specific language governing permissions and        %
00032 %  limitations under the License.                                             %
00033 %                                                                             %
00034 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00035 %
00036 %
00037 %
00038 */
00039 
00040 /*
00041   Include declarations.
00042 */
00043 #include "MagickCore/studio.h"
00044 #include "MagickCore/artifact.h"
00045 #include "MagickCore/cache-view.h"
00046 #include "MagickCore/client.h"
00047 #include "MagickCore/color.h"
00048 #include "MagickCore/color-private.h"
00049 #include "MagickCore/colorspace.h"
00050 #include "MagickCore/colorspace-private.h"
00051 #include "MagickCore/compare.h"
00052 #include "MagickCore/composite-private.h"
00053 #include "MagickCore/constitute.h"
00054 #include "MagickCore/exception-private.h"
00055 #include "MagickCore/geometry.h"
00056 #include "MagickCore/image-private.h"
00057 #include "MagickCore/list.h"
00058 #include "MagickCore/log.h"
00059 #include "MagickCore/memory_.h"
00060 #include "MagickCore/monitor.h"
00061 #include "MagickCore/monitor-private.h"
00062 #include "MagickCore/option.h"
00063 #include "MagickCore/pixel-accessor.h"
00064 #include "MagickCore/resource_.h"
00065 #include "MagickCore/string_.h"
00066 #include "MagickCore/statistic.h"
00067 #include "MagickCore/transform.h"
00068 #include "MagickCore/utility.h"
00069 #include "MagickCore/version.h"
00070 
00071 /*
00072 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00073 %                                                                             %
00074 %                                                                             %
00075 %                                                                             %
00076 %   C o m p a r e I m a g e                                                   %
00077 %                                                                             %
00078 %                                                                             %
00079 %                                                                             %
00080 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00081 %
00082 %  CompareImages() compares one or more pixel channels of an image to a 
00083 %  reconstructed image and returns the difference image.
00084 %
00085 %  The format of the CompareImages method is:
00086 %
00087 %      Image *CompareImages(const Image *image,const Image *reconstruct_image,
00088 %        const MetricType metric,double *distortion,ExceptionInfo *exception)
00089 %
00090 %  A description of each parameter follows:
00091 %
00092 %    o image: the image.
00093 %
00094 %    o reconstruct_image: the reconstruct image.
00095 %
00096 %    o metric: the metric.
00097 %
00098 %    o distortion: the computed distortion between the images.
00099 %
00100 %    o exception: return any errors or warnings in this structure.
00101 %
00102 */
00103 MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
00104   const MetricType metric,double *distortion,ExceptionInfo *exception)
00105 {
00106   CacheView
00107     *highlight_view,
00108     *image_view,
00109     *reconstruct_view;
00110 
00111   const char
00112     *artifact;
00113 
00114   Image
00115     *difference_image,
00116     *highlight_image;
00117 
00118   MagickBooleanType
00119     status;
00120 
00121   PixelInfo
00122     highlight,
00123     lowlight;
00124 
00125   ssize_t
00126     y;
00127 
00128   assert(image != (Image *) NULL);
00129   assert(image->signature == MagickSignature);
00130   if (image->debug != MagickFalse)
00131     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
00132   assert(reconstruct_image != (const Image *) NULL);
00133   assert(reconstruct_image->signature == MagickSignature);
00134   assert(distortion != (double *) NULL);
00135   *distortion=0.0;
00136   if (image->debug != MagickFalse)
00137     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
00138   if ((reconstruct_image->columns != image->columns) ||
00139       (reconstruct_image->rows != image->rows))
00140     ThrowImageException(ImageError,"ImageSizeDiffers");
00141   status=GetImageDistortion(image,reconstruct_image,metric,distortion,
00142     exception);
00143   if (status == MagickFalse)
00144     return((Image *) NULL);
00145   difference_image=CloneImage(image,0,0,MagickTrue,exception);
00146   if (difference_image == (Image *) NULL)
00147     return((Image *) NULL);
00148   (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception);
00149   highlight_image=CloneImage(image,image->columns,image->rows,MagickTrue,
00150     exception);
00151   if (highlight_image == (Image *) NULL)
00152     {
00153       difference_image=DestroyImage(difference_image);
00154       return((Image *) NULL);
00155     }
00156   status=SetImageStorageClass(highlight_image,DirectClass,exception);
00157   if (status == MagickFalse)
00158     {
00159       difference_image=DestroyImage(difference_image);
00160       highlight_image=DestroyImage(highlight_image);
00161       return((Image *) NULL);
00162     }
00163   (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception);
00164   (void) QueryColorCompliance("#f1001e33",AllCompliance,&highlight,exception);
00165   artifact=GetImageArtifact(image,"highlight-color");
00166   if (artifact != (const char *) NULL)
00167     (void) QueryColorCompliance(artifact,AllCompliance,&highlight,exception);
00168   (void) QueryColorCompliance("#ffffff33",AllCompliance,&lowlight,exception);
00169   artifact=GetImageArtifact(image,"lowlight-color");
00170   if (artifact != (const char *) NULL)
00171     (void) QueryColorCompliance(artifact,AllCompliance,&lowlight,exception);
00172   /*
00173     Generate difference image.
00174   */
00175   status=MagickTrue;
00176   image_view=AcquireCacheView(image);
00177   reconstruct_view=AcquireCacheView(reconstruct_image);
00178   highlight_view=AcquireCacheView(highlight_image);
00179 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00180   #pragma omp parallel for schedule(static,4) shared(status)
00181 #endif
00182   for (y=0; y < (ssize_t) image->rows; y++)
00183   {
00184     MagickBooleanType
00185       sync;
00186 
00187     register const Quantum
00188       *restrict p,
00189       *restrict q;
00190 
00191     register Quantum
00192       *restrict r;
00193 
00194     register ssize_t
00195       x;
00196 
00197     if (status == MagickFalse)
00198       continue;
00199     p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
00200     q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
00201       1,exception);
00202     r=QueueCacheViewAuthenticPixels(highlight_view,0,y,highlight_image->columns,
00203       1,exception);
00204     if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
00205         (r == (Quantum *) NULL))
00206       {
00207         status=MagickFalse;
00208         continue;
00209       }
00210     for (x=0; x < (ssize_t) image->columns; x++)
00211     {
00212       MagickStatusType
00213         difference;
00214 
00215       register ssize_t
00216         i;
00217 
00218       if (GetPixelMask(image,p) != 0)
00219         {
00220           SetPixelInfoPixel(highlight_image,&lowlight,r);
00221           p+=GetPixelChannels(image);
00222           q+=GetPixelChannels(reconstruct_image);
00223           r+=GetPixelChannels(highlight_image);
00224           continue;
00225         }
00226       difference=MagickFalse;
00227       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
00228       {
00229         MagickRealType
00230           distance;
00231 
00232         PixelChannel
00233           channel;
00234 
00235         PixelTrait
00236           reconstruct_traits,
00237           traits;
00238 
00239         channel=GetPixelChannelMapChannel(image,i);
00240         traits=GetPixelChannelMapTraits(image,channel);
00241         reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
00242         if ((traits == UndefinedPixelTrait) ||
00243             (reconstruct_traits == UndefinedPixelTrait) ||
00244             ((reconstruct_traits & UpdatePixelTrait) == 0))
00245           continue;
00246         distance=p[i]-(MagickRealType) GetPixelChannel(reconstruct_image,
00247           channel,q);
00248         if (fabs((double) distance) >= MagickEpsilon)
00249           difference=MagickTrue;
00250       }
00251       if (difference == MagickFalse)
00252         SetPixelInfoPixel(highlight_image,&lowlight,r);
00253       else
00254         SetPixelInfoPixel(highlight_image,&highlight,r);
00255       p+=GetPixelChannels(image);
00256       q+=GetPixelChannels(reconstruct_image);
00257       r+=GetPixelChannels(highlight_image);
00258     }
00259     sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
00260     if (sync == MagickFalse)
00261       status=MagickFalse;
00262   }
00263   highlight_view=DestroyCacheView(highlight_view);
00264   reconstruct_view=DestroyCacheView(reconstruct_view);
00265   image_view=DestroyCacheView(image_view);
00266   (void) CompositeImage(difference_image,image->compose,highlight_image,0,0,
00267     exception);
00268   highlight_image=DestroyImage(highlight_image);
00269   if (status == MagickFalse)
00270     difference_image=DestroyImage(difference_image);
00271   return(difference_image);
00272 }
00273 
00274 /*
00275 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00276 %                                                                             %
00277 %                                                                             %
00278 %                                                                             %
00279 %   G e t I m a g e D i s t o r t i o n                                       %
00280 %                                                                             %
00281 %                                                                             %
00282 %                                                                             %
00283 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00284 %
00285 %  GetImageDistortion() compares one or more pixel channels of an image to a 
00286 %  reconstructed image and returns the specified distortion metric.
00287 %
00288 %  The format of the CompareImages method is:
00289 %
00290 %      MagickBooleanType GetImageDistortion(const Image *image,
00291 %        const Image *reconstruct_image,const MetricType metric,
00292 %        double *distortion,ExceptionInfo *exception)
00293 %
00294 %  A description of each parameter follows:
00295 %
00296 %    o image: the image.
00297 %
00298 %    o reconstruct_image: the reconstruct image.
00299 %
00300 %    o metric: the metric.
00301 %
00302 %    o distortion: the computed distortion between the images.
00303 %
00304 %    o exception: return any errors or warnings in this structure.
00305 %
00306 */
00307 
00308 static MagickBooleanType GetAbsoluteDistortion(const Image *image,
00309   const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
00310 {
00311   CacheView
00312     *image_view,
00313     *reconstruct_view;
00314 
00315   MagickBooleanType
00316     status;
00317 
00318   ssize_t
00319     y;
00320 
00321   /*
00322     Compute the absolute difference in pixels between two images.
00323   */
00324   status=MagickTrue;
00325   image_view=AcquireCacheView(image);
00326   reconstruct_view=AcquireCacheView(reconstruct_image);
00327 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00328   #pragma omp parallel for schedule(static,4) shared(status)
00329 #endif
00330   for (y=0; y < (ssize_t) image->rows; y++)
00331   {
00332     double
00333       channel_distortion[MaxPixelChannels+1];
00334 
00335     register const Quantum
00336       *restrict p,
00337       *restrict q;
00338 
00339     register ssize_t
00340       i,
00341       x;
00342 
00343     if (status == MagickFalse)
00344       continue;
00345     p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
00346     q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
00347       1,exception);
00348     if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
00349       {
00350         status=MagickFalse;
00351         continue;
00352       }
00353     (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
00354     for (x=0; x < (ssize_t) image->columns; x++)
00355     {
00356       MagickBooleanType
00357         difference;
00358 
00359       register ssize_t
00360         i;
00361 
00362       if (GetPixelMask(image,p) != 0)
00363         {
00364           p+=GetPixelChannels(image);
00365           q+=GetPixelChannels(reconstruct_image);
00366           continue;
00367         }
00368       difference=MagickFalse;
00369       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
00370       {
00371         PixelChannel
00372           channel;
00373 
00374         PixelTrait
00375           reconstruct_traits,
00376           traits;
00377 
00378         channel=GetPixelChannelMapChannel(image,i);
00379         traits=GetPixelChannelMapTraits(image,channel);
00380         reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
00381         if ((traits == UndefinedPixelTrait) ||
00382             (reconstruct_traits == UndefinedPixelTrait) ||
00383             ((reconstruct_traits & UpdatePixelTrait) == 0))
00384           continue;
00385         if (p[i] != GetPixelChannel(reconstruct_image,channel,q))
00386           difference=MagickTrue;
00387       }
00388       if (difference != MagickFalse)
00389         {
00390           channel_distortion[i]++;
00391           channel_distortion[CompositePixelChannel]++;
00392         }
00393       p+=GetPixelChannels(image);
00394       q+=GetPixelChannels(reconstruct_image);
00395     }
00396 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00397   #pragma omp critical (MagickCore_GetAbsoluteError)
00398 #endif
00399     for (i=0; i <= MaxPixelChannels; i++)
00400       distortion[i]+=channel_distortion[i];
00401   }
00402   reconstruct_view=DestroyCacheView(reconstruct_view);
00403   image_view=DestroyCacheView(image_view);
00404   return(status);
00405 }
00406 
00407 static size_t GetImageChannels(const Image *image)
00408 {
00409   register ssize_t
00410     i;
00411 
00412   size_t
00413     channels;
00414 
00415   channels=0;
00416   for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
00417   {
00418     PixelChannel
00419       channel;
00420 
00421     PixelTrait
00422       traits;
00423 
00424     channel=GetPixelChannelMapChannel(image,i);
00425     traits=GetPixelChannelMapTraits(image,channel);
00426     if ((traits & UpdatePixelTrait) != 0)
00427       channels++;
00428   }
00429   return(channels);
00430 }
00431 
00432 static MagickBooleanType GetFuzzDistortion(const Image *image,
00433   const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
00434 {
00435   CacheView
00436     *image_view,
00437     *reconstruct_view;
00438 
00439   MagickBooleanType
00440     status;
00441 
00442   register ssize_t
00443     i;
00444 
00445   ssize_t
00446     y;
00447 
00448   status=MagickTrue;
00449   image_view=AcquireCacheView(image);
00450   reconstruct_view=AcquireCacheView(reconstruct_image);
00451 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00452   #pragma omp parallel for schedule(static,4) shared(status)
00453 #endif
00454   for (y=0; y < (ssize_t) image->rows; y++)
00455   {
00456     double
00457       channel_distortion[MaxPixelChannels+1];
00458 
00459     register const Quantum
00460       *restrict p,
00461       *restrict q;
00462 
00463     register ssize_t
00464       i,
00465       x;
00466 
00467     if (status == MagickFalse)
00468       continue;
00469     p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
00470     q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
00471       1,exception);
00472     if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
00473       {
00474         status=MagickFalse;
00475         continue;
00476       }
00477     (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
00478     for (x=0; x < (ssize_t) image->columns; x++)
00479     {
00480       register ssize_t
00481         i;
00482 
00483       if (GetPixelMask(image,p) != 0)
00484         {
00485           p+=GetPixelChannels(image);
00486           q+=GetPixelChannels(reconstruct_image);
00487           continue;
00488         }
00489       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
00490       {
00491         MagickRealType
00492           distance;
00493 
00494         PixelChannel
00495           channel;
00496 
00497         PixelTrait
00498           reconstruct_traits,
00499           traits;
00500 
00501         channel=GetPixelChannelMapChannel(image,i);
00502         traits=GetPixelChannelMapTraits(image,channel);
00503         reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
00504         if ((traits == UndefinedPixelTrait) ||
00505             (reconstruct_traits == UndefinedPixelTrait) ||
00506             ((reconstruct_traits & UpdatePixelTrait) == 0))
00507           continue;
00508         distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
00509           reconstruct_image,channel,q));
00510         distance*=distance;
00511         channel_distortion[i]+=distance;
00512         channel_distortion[CompositePixelChannel]+=distance;
00513       }
00514       p+=GetPixelChannels(image);
00515       q+=GetPixelChannels(reconstruct_image);
00516     }
00517 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00518   #pragma omp critical (MagickCore_GetMeanSquaredError)
00519 #endif
00520     for (i=0; i <= MaxPixelChannels; i++)
00521       distortion[i]+=channel_distortion[i];
00522   }
00523   reconstruct_view=DestroyCacheView(reconstruct_view);
00524   image_view=DestroyCacheView(image_view);
00525   for (i=0; i <= MaxPixelChannels; i++)
00526     distortion[i]/=((double) image->columns*image->rows);
00527   distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
00528   distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
00529   return(status);
00530 }
00531 
00532 static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
00533   const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
00534 {
00535   CacheView
00536     *image_view,
00537     *reconstruct_view;
00538 
00539   MagickBooleanType
00540     status;
00541 
00542   register ssize_t
00543     i;
00544 
00545   ssize_t
00546     y;
00547 
00548   status=MagickTrue;
00549   image_view=AcquireCacheView(image);
00550   reconstruct_view=AcquireCacheView(reconstruct_image);
00551 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00552   #pragma omp parallel for schedule(static,4) shared(status)
00553 #endif
00554   for (y=0; y < (ssize_t) image->rows; y++)
00555   {
00556     double
00557       channel_distortion[MaxPixelChannels+1];
00558 
00559     register const Quantum
00560       *restrict p,
00561       *restrict q;
00562 
00563     register ssize_t
00564       i,
00565       x;
00566 
00567     if (status == MagickFalse)
00568       continue;
00569     p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
00570     q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
00571       1,exception);
00572     if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
00573       {
00574         status=MagickFalse;
00575         continue;
00576       }
00577     (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
00578     for (x=0; x < (ssize_t) image->columns; x++)
00579     {
00580       register ssize_t
00581         i;
00582 
00583       if (GetPixelMask(image,p) != 0)
00584         {
00585           p+=GetPixelChannels(image);
00586           q+=GetPixelChannels(reconstruct_image);
00587           continue;
00588         }
00589       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
00590       {
00591         MagickRealType
00592           distance;
00593 
00594         PixelChannel
00595           channel;
00596 
00597         PixelTrait
00598           reconstruct_traits,
00599           traits;
00600 
00601         channel=GetPixelChannelMapChannel(image,i);
00602         traits=GetPixelChannelMapTraits(image,channel);
00603         reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
00604         if ((traits == UndefinedPixelTrait) ||
00605             (reconstruct_traits == UndefinedPixelTrait) ||
00606             ((reconstruct_traits & UpdatePixelTrait) == 0))
00607           continue;
00608         distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
00609           reconstruct_image,channel,q));
00610         channel_distortion[i]+=distance;
00611         channel_distortion[CompositePixelChannel]+=distance;
00612       }
00613       p+=GetPixelChannels(image);
00614       q+=GetPixelChannels(reconstruct_image);
00615     }
00616 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00617   #pragma omp critical (MagickCore_GetMeanAbsoluteError)
00618 #endif
00619     for (i=0; i <= MaxPixelChannels; i++)
00620       distortion[i]+=channel_distortion[i];
00621   }
00622   reconstruct_view=DestroyCacheView(reconstruct_view);
00623   image_view=DestroyCacheView(image_view);
00624   for (i=0; i <= MaxPixelChannels; i++)
00625     distortion[i]/=((double) image->columns*image->rows);
00626   distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
00627   return(status);
00628 }
00629 
00630 static MagickBooleanType GetMeanErrorPerPixel(Image *image,
00631   const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
00632 {
00633   CacheView
00634     *image_view,
00635     *reconstruct_view;
00636 
00637   MagickBooleanType
00638     status;
00639 
00640   MagickRealType
00641     alpha,
00642     area,
00643     beta,
00644     maximum_error,
00645     mean_error;
00646 
00647   ssize_t
00648     y;
00649 
00650   status=MagickTrue;
00651   alpha=1.0;
00652   beta=1.0;
00653   area=0.0;
00654   maximum_error=0.0;
00655   mean_error=0.0;
00656   image_view=AcquireCacheView(image);
00657   reconstruct_view=AcquireCacheView(reconstruct_image);
00658   for (y=0; y < (ssize_t) image->rows; y++)
00659   {
00660     register const Quantum
00661       *restrict p,
00662       *restrict q;
00663 
00664     register ssize_t
00665       x;
00666 
00667     p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
00668     q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
00669       1,exception);
00670     if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
00671       {
00672         status=MagickFalse;
00673         break;
00674       }
00675     for (x=0; x < (ssize_t) image->columns; x++)
00676     {
00677       register ssize_t
00678         i;
00679 
00680       if (GetPixelMask(image,p) != 0)
00681         {
00682           p+=GetPixelChannels(image);
00683           q+=GetPixelChannels(reconstruct_image);
00684           continue;
00685         }
00686       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
00687       {
00688         MagickRealType
00689           distance;
00690 
00691         PixelChannel
00692           channel;
00693 
00694         PixelTrait
00695           reconstruct_traits,
00696           traits;
00697 
00698         channel=GetPixelChannelMapChannel(image,i);
00699         traits=GetPixelChannelMapTraits(image,channel);
00700         reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
00701         if ((traits == UndefinedPixelTrait) ||
00702             (reconstruct_traits == UndefinedPixelTrait) ||
00703             ((reconstruct_traits & UpdatePixelTrait) == 0))
00704           continue;
00705         distance=fabs((double) (alpha*p[i]-beta*GetPixelChannel(
00706           reconstruct_image,channel,q)));
00707         distortion[i]+=distance;
00708         distortion[CompositePixelChannel]+=distance;
00709         mean_error+=distance*distance;
00710         if (distance > maximum_error)
00711           maximum_error=distance;
00712         area++;
00713       }
00714       p+=GetPixelChannels(image);
00715       q+=GetPixelChannels(reconstruct_image);
00716     }
00717   }
00718   reconstruct_view=DestroyCacheView(reconstruct_view);
00719   image_view=DestroyCacheView(image_view);
00720   image->error.mean_error_per_pixel=distortion[CompositePixelChannel]/area;
00721   image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
00722   image->error.normalized_maximum_error=QuantumScale*maximum_error;
00723   return(status);
00724 }
00725 
00726 static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
00727   const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
00728 {
00729   CacheView
00730     *image_view,
00731     *reconstruct_view;
00732 
00733   MagickBooleanType
00734     status;
00735 
00736   register ssize_t
00737     i;
00738 
00739   ssize_t
00740     y;
00741 
00742   status=MagickTrue;
00743   image_view=AcquireCacheView(image);
00744   reconstruct_view=AcquireCacheView(reconstruct_image);
00745 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00746   #pragma omp parallel for schedule(static,4) shared(status)
00747 #endif
00748   for (y=0; y < (ssize_t) image->rows; y++)
00749   {
00750     double
00751       channel_distortion[MaxPixelChannels+1];
00752 
00753     register const Quantum
00754       *restrict p,
00755       *restrict q;
00756 
00757     register ssize_t
00758       i,
00759       x;
00760 
00761     if (status == MagickFalse)
00762       continue;
00763     p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
00764     q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
00765       1,exception);
00766     if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
00767       {
00768         status=MagickFalse;
00769         continue;
00770       }
00771     (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
00772     for (x=0; x < (ssize_t) image->columns; x++)
00773     {
00774       register ssize_t
00775         i;
00776 
00777       if (GetPixelMask(image,p) != 0)
00778         {
00779           p+=GetPixelChannels(image);
00780           q+=GetPixelChannels(reconstruct_image);
00781           continue;
00782         }
00783       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
00784       {
00785         MagickRealType
00786           distance;
00787 
00788         PixelChannel
00789           channel;
00790 
00791         PixelTrait
00792           reconstruct_traits,
00793           traits;
00794 
00795         channel=GetPixelChannelMapChannel(image,i);
00796         traits=GetPixelChannelMapTraits(image,channel);
00797         reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
00798         if ((traits == UndefinedPixelTrait) ||
00799             (reconstruct_traits == UndefinedPixelTrait) ||
00800             ((reconstruct_traits & UpdatePixelTrait) == 0))
00801           continue;
00802         distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
00803           reconstruct_image,channel,q));
00804         distance*=distance;
00805         channel_distortion[i]+=distance;
00806         channel_distortion[CompositePixelChannel]+=distance;
00807       }
00808       p+=GetPixelChannels(image);
00809       q+=GetPixelChannels(reconstruct_image);
00810     }
00811 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00812   #pragma omp critical (MagickCore_GetMeanSquaredError)
00813 #endif
00814     for (i=0; i <= MaxPixelChannels; i++)
00815       distortion[i]+=channel_distortion[i];
00816   }
00817   reconstruct_view=DestroyCacheView(reconstruct_view);
00818   image_view=DestroyCacheView(image_view);
00819   for (i=0; i <= MaxPixelChannels; i++)
00820     distortion[i]/=((double) image->columns*image->rows);
00821   distortion[CompositePixelChannel]/=GetImageChannels(image);
00822   return(status);
00823 }
00824 
00825 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
00826   const Image *image,const Image *reconstruct_image,double *distortion,
00827   ExceptionInfo *exception)
00828 {
00829 #define SimilarityImageTag  "Similarity/Image"
00830 
00831   CacheView
00832     *image_view,
00833     *reconstruct_view;
00834 
00835   ChannelStatistics
00836     *image_statistics,
00837     *reconstruct_statistics;
00838 
00839   MagickBooleanType
00840     status;
00841 
00842   MagickOffsetType
00843     progress;
00844 
00845   MagickRealType
00846     area;
00847 
00848   register ssize_t
00849     i;
00850 
00851   ssize_t
00852     y;
00853 
00854   /*
00855     Normalize to account for variation due to lighting and exposure condition.
00856   */
00857   image_statistics=GetImageStatistics(image,exception);
00858   reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
00859   status=MagickTrue;
00860   progress=0;
00861   for (i=0; i <= MaxPixelChannels; i++)
00862     distortion[i]=0.0;
00863   area=1.0/((MagickRealType) image->columns*image->rows-1);
00864   image_view=AcquireCacheView(image);
00865   reconstruct_view=AcquireCacheView(reconstruct_image);
00866   for (y=0; y < (ssize_t) image->rows; y++)
00867   {
00868     register const Quantum
00869       *restrict p,
00870       *restrict q;
00871 
00872     register ssize_t
00873       x;
00874 
00875     if (status == MagickFalse)
00876       continue;
00877     p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
00878     q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
00879       1,exception);
00880     if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
00881       {
00882         status=MagickFalse;
00883         continue;
00884       }
00885     for (x=0; x < (ssize_t) image->columns; x++)
00886     {
00887       register ssize_t
00888         i;
00889 
00890       if (GetPixelMask(image,p) != 0)
00891         {
00892           p+=GetPixelChannels(image);
00893           q+=GetPixelChannels(reconstruct_image);
00894           continue;
00895         }
00896       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
00897       {
00898         PixelChannel
00899           channel;
00900 
00901         PixelTrait
00902           reconstruct_traits,
00903           traits;
00904 
00905         channel=GetPixelChannelMapChannel(image,i);
00906         traits=GetPixelChannelMapTraits(image,channel);
00907         reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
00908         if ((traits == UndefinedPixelTrait) ||
00909             (reconstruct_traits == UndefinedPixelTrait) ||
00910             ((reconstruct_traits & UpdatePixelTrait) == 0))
00911           continue;
00912         distortion[i]+=area*QuantumScale*(p[i]-image_statistics[i].mean)*
00913           (GetPixelChannel(reconstruct_image,channel,q)-
00914           reconstruct_statistics[channel].mean);
00915       }
00916       p+=GetPixelChannels(image);
00917       q+=GetPixelChannels(reconstruct_image);
00918     }
00919     if (image->progress_monitor != (MagickProgressMonitor) NULL)
00920       {
00921         MagickBooleanType
00922           proceed;
00923 
00924         proceed=SetImageProgress(image,SimilarityImageTag,progress++,
00925           image->rows);
00926         if (proceed == MagickFalse)
00927           status=MagickFalse;
00928       }
00929   }
00930   reconstruct_view=DestroyCacheView(reconstruct_view);
00931   image_view=DestroyCacheView(image_view);
00932   /*
00933     Divide by the standard deviation.
00934   */
00935   distortion[CompositePixelChannel]=0.0;
00936   for (i=0; i < MaxPixelChannels; i++)
00937   {
00938     MagickRealType
00939       gamma;
00940 
00941     PixelChannel
00942       channel;
00943 
00944     channel=GetPixelChannelMapChannel(image,i);
00945     gamma=image_statistics[i].standard_deviation*
00946       reconstruct_statistics[channel].standard_deviation;
00947     gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
00948     distortion[i]=QuantumRange*gamma*distortion[i];
00949     distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
00950   }
00951   distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
00952     GetImageChannels(image));
00953   /*
00954     Free resources.
00955   */
00956   reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
00957     reconstruct_statistics);
00958   image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
00959     image_statistics);
00960   return(status);
00961 }
00962 
00963 static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
00964   const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
00965 {
00966   CacheView
00967     *image_view,
00968     *reconstruct_view;
00969 
00970   MagickBooleanType
00971     status;
00972 
00973   ssize_t
00974     y;
00975 
00976   status=MagickTrue;
00977   image_view=AcquireCacheView(image);
00978   reconstruct_view=AcquireCacheView(reconstruct_image);
00979 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00980   #pragma omp parallel for schedule(static,4) shared(status)
00981 #endif
00982   for (y=0; y < (ssize_t) image->rows; y++)
00983   {
00984     double
00985       channel_distortion[MaxPixelChannels+1];
00986 
00987     register const Quantum
00988       *restrict p,
00989       *restrict q;
00990 
00991     register ssize_t
00992       i,
00993       x;
00994 
00995     if (status == MagickFalse)
00996       continue;
00997     p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
00998     q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
00999       1,exception);
01000     if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
01001       {
01002         status=MagickFalse;
01003         continue;
01004       }
01005     (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
01006     for (x=0; x < (ssize_t) image->columns; x++)
01007     {
01008       register ssize_t
01009         i;
01010 
01011       if (GetPixelMask(image,p) != 0)
01012         {
01013           p+=GetPixelChannels(image);
01014           q+=GetPixelChannels(reconstruct_image);
01015           continue;
01016         }
01017       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
01018       {
01019         MagickRealType
01020           distance;
01021 
01022         PixelChannel
01023           channel;
01024 
01025         PixelTrait
01026           reconstruct_traits,
01027           traits;
01028 
01029         channel=GetPixelChannelMapChannel(image,i);
01030         traits=GetPixelChannelMapTraits(image,channel);
01031         reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
01032         if ((traits == UndefinedPixelTrait) ||
01033             (reconstruct_traits == UndefinedPixelTrait) ||
01034             ((reconstruct_traits & UpdatePixelTrait) == 0))
01035           continue;
01036         distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
01037           reconstruct_image,channel,q));
01038         if (distance > channel_distortion[i])
01039           channel_distortion[i]=distance;
01040         if (distance > channel_distortion[CompositePixelChannel])
01041           channel_distortion[CompositePixelChannel]=distance;
01042       }
01043       p+=GetPixelChannels(image);
01044       q+=GetPixelChannels(reconstruct_image);
01045     }
01046 #if defined(MAGICKCORE_OPENMP_SUPPORT)
01047   #pragma omp critical (MagickCore_GetPeakAbsoluteError)
01048 #endif
01049     for (i=0; i <= MaxPixelChannels; i++)
01050       if (channel_distortion[i] > distortion[i])
01051         distortion[i]=channel_distortion[i];
01052   }
01053   reconstruct_view=DestroyCacheView(reconstruct_view);
01054   image_view=DestroyCacheView(image_view);
01055   return(status);
01056 }
01057 
01058 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
01059   const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
01060 {
01061   MagickBooleanType
01062     status;
01063 
01064   register ssize_t
01065     i;
01066 
01067   status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
01068   for (i=0; i <= MaxPixelChannels; i++)
01069     distortion[i]=20.0*log10((double) 1.0/sqrt(distortion[i]));
01070   return(status);
01071 }
01072 
01073 static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
01074   const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
01075 {
01076   MagickBooleanType
01077     status;
01078 
01079   register ssize_t
01080     i;
01081 
01082   status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
01083   for (i=0; i <= MaxPixelChannels; i++)
01084     distortion[i]=sqrt(distortion[i]);
01085   return(status);
01086 }
01087 
01088 MagickExport MagickBooleanType GetImageDistortion(Image *image,
01089   const Image *reconstruct_image,const MetricType metric,double *distortion,
01090   ExceptionInfo *exception)
01091 {
01092   double
01093     *channel_distortion;
01094 
01095   MagickBooleanType
01096     status;
01097 
01098   size_t
01099     length;
01100 
01101   assert(image != (Image *) NULL);
01102   assert(image->signature == MagickSignature);
01103   if (image->debug != MagickFalse)
01104     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
01105   assert(reconstruct_image != (const Image *) NULL);
01106   assert(reconstruct_image->signature == MagickSignature);
01107   assert(distortion != (double *) NULL);
01108   *distortion=0.0;
01109   if (image->debug != MagickFalse)
01110     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
01111   if ((reconstruct_image->columns != image->columns) ||
01112       (reconstruct_image->rows != image->rows))
01113     ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
01114   /*
01115     Get image distortion.
01116   */
01117   length=MaxPixelChannels+1;
01118   channel_distortion=(double *) AcquireQuantumMemory(length,
01119     sizeof(*channel_distortion));
01120   if (channel_distortion == (double *) NULL)
01121     ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
01122   (void) ResetMagickMemory(channel_distortion,0,length*
01123     sizeof(*channel_distortion));
01124   switch (metric)
01125   {
01126     case AbsoluteErrorMetric:
01127     {
01128       status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
01129         exception);
01130       break;
01131     }
01132     case FuzzErrorMetric:
01133     {
01134       status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
01135         exception);
01136       break;
01137     }
01138     case MeanAbsoluteErrorMetric:
01139     {
01140       status=GetMeanAbsoluteDistortion(image,reconstruct_image,
01141         channel_distortion,exception);
01142       break;
01143     }
01144     case MeanErrorPerPixelMetric:
01145     {
01146       status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
01147         exception);
01148       break;
01149     }
01150     case MeanSquaredErrorMetric:
01151     {
01152       status=GetMeanSquaredDistortion(image,reconstruct_image,
01153         channel_distortion,exception);
01154       break;
01155     }
01156     case NormalizedCrossCorrelationErrorMetric:
01157     default:
01158     {
01159       status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
01160         channel_distortion,exception);
01161       break;
01162     }
01163     case PeakAbsoluteErrorMetric:
01164     {
01165       status=GetPeakAbsoluteDistortion(image,reconstruct_image,
01166         channel_distortion,exception);
01167       break;
01168     }
01169     case PeakSignalToNoiseRatioMetric:
01170     {
01171       status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
01172         channel_distortion,exception);
01173       break;
01174     }
01175     case RootMeanSquaredErrorMetric:
01176     {
01177       status=GetRootMeanSquaredDistortion(image,reconstruct_image,
01178         channel_distortion,exception);
01179       break;
01180     }
01181   }
01182   *distortion=channel_distortion[CompositePixelChannel];
01183   channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
01184   return(status);
01185 }
01186 
01187 /*
01188 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
01189 %                                                                             %
01190 %                                                                             %
01191 %                                                                             %
01192 %   G e t I m a g e D i s t o r t i o n s                                     %
01193 %                                                                             %
01194 %                                                                             %
01195 %                                                                             %
01196 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
01197 %
01198 %  GetImageDistrortion() compares the pixel channels of an image to a
01199 %  reconstructed image and returns the specified distortion metric for each
01200 %  channel.
01201 %
01202 %  The format of the CompareImages method is:
01203 %
01204 %      double *GetImageDistortions(const Image *image,
01205 %        const Image *reconstruct_image,const MetricType metric,
01206 %        ExceptionInfo *exception)
01207 %
01208 %  A description of each parameter follows:
01209 %
01210 %    o image: the image.
01211 %
01212 %    o reconstruct_image: the reconstruct image.
01213 %
01214 %    o metric: the metric.
01215 %
01216 %    o exception: return any errors or warnings in this structure.
01217 %
01218 */
01219 MagickExport double *GetImageDistortions(Image *image,
01220   const Image *reconstruct_image,const MetricType metric,
01221   ExceptionInfo *exception)
01222 {
01223   double
01224     *channel_distortion;
01225 
01226   MagickBooleanType
01227     status;
01228 
01229   size_t
01230     length;
01231 
01232   assert(image != (Image *) NULL);
01233   assert(image->signature == MagickSignature);
01234   if (image->debug != MagickFalse)
01235     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
01236   assert(reconstruct_image != (const Image *) NULL);
01237   assert(reconstruct_image->signature == MagickSignature);
01238   if (image->debug != MagickFalse)
01239     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
01240   if ((reconstruct_image->columns != image->columns) ||
01241       (reconstruct_image->rows != image->rows))
01242     {
01243       (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
01244         "ImageSizeDiffers","`%s'",image->filename);
01245       return((double *) NULL);
01246     }
01247   /*
01248     Get image distortion.
01249   */
01250   length=MaxPixelChannels+1UL;
01251   channel_distortion=(double *) AcquireQuantumMemory(length,
01252     sizeof(*channel_distortion));
01253   if (channel_distortion == (double *) NULL)
01254     ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
01255   (void) ResetMagickMemory(channel_distortion,0,length*
01256     sizeof(*channel_distortion));
01257   status=MagickTrue;
01258   switch (metric)
01259   {
01260     case AbsoluteErrorMetric:
01261     {
01262       status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
01263         exception);
01264       break;
01265     }
01266     case FuzzErrorMetric:
01267     {
01268       status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
01269         exception);
01270       break;
01271     }
01272     case MeanAbsoluteErrorMetric:
01273     {
01274       status=GetMeanAbsoluteDistortion(image,reconstruct_image,
01275         channel_distortion,exception);
01276       break;
01277     }
01278     case MeanErrorPerPixelMetric:
01279     {
01280       status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
01281         exception);
01282       break;
01283     }
01284     case MeanSquaredErrorMetric:
01285     {
01286       status=GetMeanSquaredDistortion(image,reconstruct_image,
01287         channel_distortion,exception);
01288       break;
01289     }
01290     case NormalizedCrossCorrelationErrorMetric:
01291     default:
01292     {
01293       status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
01294         channel_distortion,exception);
01295       break;
01296     }
01297     case PeakAbsoluteErrorMetric:
01298     {
01299       status=GetPeakAbsoluteDistortion(image,reconstruct_image,
01300         channel_distortion,exception);
01301       break;
01302     }
01303     case PeakSignalToNoiseRatioMetric:
01304     {
01305       status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
01306         channel_distortion,exception);
01307       break;
01308     }
01309     case RootMeanSquaredErrorMetric:
01310     {
01311       status=GetRootMeanSquaredDistortion(image,reconstruct_image,
01312         channel_distortion,exception);
01313       break;
01314     }
01315   }
01316   if (status == MagickFalse)
01317     {
01318       channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
01319       return((double *) NULL);
01320     }
01321   return(channel_distortion);
01322 }
01323 
01324 /*
01325 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
01326 %                                                                             %
01327 %                                                                             %
01328 %                                                                             %
01329 %  I s I m a g e s E q u a l                                                  %
01330 %                                                                             %
01331 %                                                                             %
01332 %                                                                             %
01333 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
01334 %
01335 %  IsImagesEqual() measures the difference between colors at each pixel
01336 %  location of two images.  A value other than 0 means the colors match
01337 %  exactly.  Otherwise an error measure is computed by summing over all
01338 %  pixels in an image the distance squared in RGB space between each image
01339 %  pixel and its corresponding pixel in the reconstruct image.  The error
01340 %  measure is assigned to these image members:
01341 %
01342 %    o mean_error_per_pixel:  The mean error for any single pixel in
01343 %      the image.
01344 %
01345 %    o normalized_mean_error:  The normalized mean quantization error for
01346 %      any single pixel in the image.  This distance measure is normalized to
01347 %      a range between 0 and 1.  It is independent of the range of red, green,
01348 %      and blue values in the image.
01349 %
01350 %    o normalized_maximum_error:  The normalized maximum quantization
01351 %      error for any single pixel in the image.  This distance measure is
01352 %      normalized to a range between 0 and 1.  It is independent of the range
01353 %      of red, green, and blue values in your image.
01354 %
01355 %  A small normalized mean square error, accessed as
01356 %  image->normalized_mean_error, suggests the images are very similar in
01357 %  spatial layout and color.
01358 %
01359 %  The format of the IsImagesEqual method is:
01360 %
01361 %      MagickBooleanType IsImagesEqual(Image *image,
01362 %        const Image *reconstruct_image,ExceptionInfo *exception)
01363 %
01364 %  A description of each parameter follows.
01365 %
01366 %    o image: the image.
01367 %
01368 %    o reconstruct_image: the reconstruct image.
01369 %
01370 %    o exception: return any errors or warnings in this structure.
01371 %
01372 */
01373 MagickExport MagickBooleanType IsImagesEqual(Image *image,
01374   const Image *reconstruct_image,ExceptionInfo *exception)
01375 {
01376   CacheView
01377     *image_view,
01378     *reconstruct_view;
01379 
01380   MagickBooleanType
01381     status;
01382 
01383   MagickRealType
01384     area,
01385     maximum_error,
01386     mean_error,
01387     mean_error_per_pixel;
01388 
01389   ssize_t
01390     y;
01391 
01392   assert(image != (Image *) NULL);
01393   assert(image->signature == MagickSignature);
01394   assert(reconstruct_image != (const Image *) NULL);
01395   assert(reconstruct_image->signature == MagickSignature);
01396   if ((reconstruct_image->columns != image->columns) ||
01397       (reconstruct_image->rows != image->rows))
01398     ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
01399   area=0.0;
01400   maximum_error=0.0;
01401   mean_error_per_pixel=0.0;
01402   mean_error=0.0;
01403   image_view=AcquireCacheView(image);
01404   reconstruct_view=AcquireCacheView(reconstruct_image);
01405   for (y=0; y < (ssize_t) image->rows; y++)
01406   {
01407     register const Quantum
01408       *restrict p,
01409       *restrict q;
01410 
01411     register ssize_t
01412       x;
01413 
01414     p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
01415     q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
01416       1,exception);
01417     if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
01418       break;
01419     for (x=0; x < (ssize_t) image->columns; x++)
01420     {
01421       register ssize_t
01422         i;
01423 
01424       if (GetPixelMask(image,p) != 0)
01425         {
01426           p+=GetPixelChannels(image);
01427           q+=GetPixelChannels(reconstruct_image);
01428           continue;
01429         }
01430       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
01431       {
01432         MagickRealType
01433           distance;
01434 
01435         PixelChannel
01436           channel;
01437 
01438         PixelTrait
01439           reconstruct_traits,
01440           traits;
01441 
01442         channel=GetPixelChannelMapChannel(image,i);
01443         traits=GetPixelChannelMapTraits(image,channel);
01444         reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
01445         if ((traits == UndefinedPixelTrait) ||
01446             (reconstruct_traits == UndefinedPixelTrait) ||
01447             ((reconstruct_traits & UpdatePixelTrait) == 0))
01448           continue;
01449         distance=fabs(p[i]-(MagickRealType) GetPixelChannel(reconstruct_image,
01450           channel,q));
01451         mean_error_per_pixel+=distance;
01452         mean_error+=distance*distance;
01453         if (distance > maximum_error)
01454           maximum_error=distance;
01455         area++;
01456       }
01457       p+=GetPixelChannels(image);
01458       q+=GetPixelChannels(reconstruct_image);
01459     }
01460   }
01461   reconstruct_view=DestroyCacheView(reconstruct_view);
01462   image_view=DestroyCacheView(image_view);
01463   image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
01464   image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
01465     mean_error/area);
01466   image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
01467   status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
01468   return(status);
01469 }
01470 
01471 /*
01472 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
01473 %                                                                             %
01474 %                                                                             %
01475 %                                                                             %
01476 %   S i m i l a r i t y I m a g e                                             %
01477 %                                                                             %
01478 %                                                                             %
01479 %                                                                             %
01480 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
01481 %
01482 %  SimilarityImage() compares the reference image of the image and returns the
01483 %  best match offset.  In addition, it returns a similarity image such that an
01484 %  exact match location is completely white and if none of the pixels match,
01485 %  black, otherwise some gray level in-between.
01486 %
01487 %  The format of the SimilarityImageImage method is:
01488 %
01489 %      Image *SimilarityImage(const Image *image,const Image *reference,
01490 %        const MetricType metric,RectangleInfo *offset,double *similarity,
01491 %        ExceptionInfo *exception)
01492 %
01493 %  A description of each parameter follows:
01494 %
01495 %    o image: the image.
01496 %
01497 %    o reference: find an area of the image that closely resembles this image.
01498 %
01499 %    o metric: the metric.
01500 %
01501 %    o the best match offset of the reference image within the image.
01502 %
01503 %    o similarity: the computed similarity between the images.
01504 %
01505 %    o exception: return any errors or warnings in this structure.
01506 %
01507 */
01508 
01509 static double GetSimilarityMetric(const Image *image,const Image *reference,
01510   const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
01511   ExceptionInfo *exception)
01512 {
01513   double
01514     distortion;
01515 
01516   Image
01517     *similarity_image;
01518 
01519   MagickBooleanType
01520     status;
01521 
01522   RectangleInfo
01523     geometry;
01524 
01525   SetGeometry(reference,&geometry);
01526   geometry.x=x_offset;
01527   geometry.y=y_offset;
01528   similarity_image=CropImage(image,&geometry,exception);
01529   if (similarity_image == (Image *) NULL)
01530     return(0.0);
01531   distortion=0.0;
01532   status=GetImageDistortion(similarity_image,reference,metric,&distortion,
01533     exception);
01534   similarity_image=DestroyImage(similarity_image);
01535   if (status == MagickFalse)
01536     return(0.0);
01537   return(distortion);
01538 }
01539 
01540 MagickExport Image *SimilarityImage(Image *image,const Image *reference,
01541   const MetricType metric,RectangleInfo *offset,double *similarity_metric,
01542   ExceptionInfo *exception)
01543 {
01544 #define SimilarityImageTag  "Similarity/Image"
01545 
01546   CacheView
01547     *similarity_view;
01548 
01549   Image
01550     *similarity_image;
01551 
01552   MagickBooleanType
01553     status;
01554 
01555   MagickOffsetType
01556     progress;
01557 
01558   ssize_t
01559     y;
01560 
01561   assert(image != (const Image *) NULL);
01562   assert(image->signature == MagickSignature);
01563   if (image->debug != MagickFalse)
01564     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
01565   assert(exception != (ExceptionInfo *) NULL);
01566   assert(exception->signature == MagickSignature);
01567   assert(offset != (RectangleInfo *) NULL);
01568   SetGeometry(reference,offset);
01569   *similarity_metric=1.0;
01570   if ((reference->columns > image->columns) || (reference->rows > image->rows))
01571     ThrowImageException(ImageError,"ImageSizeDiffers");
01572   similarity_image=CloneImage(image,image->columns-reference->columns+1,
01573     image->rows-reference->rows+1,MagickTrue,exception);
01574   if (similarity_image == (Image *) NULL)
01575     return((Image *) NULL);
01576   status=SetImageStorageClass(similarity_image,DirectClass,exception);
01577   if (status == MagickFalse)
01578     {
01579       similarity_image=DestroyImage(similarity_image);
01580       return((Image *) NULL);
01581     }
01582   /*
01583     Measure similarity of reference image against image.
01584   */
01585   status=MagickTrue;
01586   progress=0;
01587   similarity_view=AcquireCacheView(similarity_image);
01588 #if defined(MAGICKCORE_OPENMP_SUPPORT)
01589   #pragma omp parallel for schedule(static,4) shared(progress,status)
01590 #endif
01591   for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
01592   {
01593     double
01594       similarity;
01595 
01596     register Quantum
01597       *restrict q;
01598 
01599     register ssize_t
01600       x;
01601 
01602     if (status == MagickFalse)
01603       continue;
01604     q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
01605       1,exception);
01606     if (q == (Quantum *) NULL)
01607       {
01608         status=MagickFalse;
01609         continue;
01610       }
01611     for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
01612     {
01613       register ssize_t
01614         i;
01615 
01616       similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
01617 #if defined(MAGICKCORE_OPENMP_SUPPORT)
01618   #pragma omp critical (MagickCore_SimilarityImage)
01619 #endif
01620       if (similarity < *similarity_metric)
01621         {
01622           *similarity_metric=similarity;
01623           offset->x=x;
01624           offset->y=y;
01625         }
01626       if (GetPixelMask(similarity_image,q) != 0)
01627         {
01628           q+=GetPixelChannels(similarity_image);
01629           continue;
01630         }
01631       for (i=0; i < (ssize_t) GetPixelChannels(similarity_image); i++)
01632       {
01633         PixelChannel
01634           channel;
01635 
01636         PixelTrait
01637           similarity_traits,
01638           traits;
01639 
01640         channel=GetPixelChannelMapChannel(image,i);
01641         traits=GetPixelChannelMapTraits(image,channel);
01642         similarity_traits=GetPixelChannelMapTraits(similarity_image,channel);
01643         if ((traits == UndefinedPixelTrait) ||
01644             (similarity_traits == UndefinedPixelTrait) ||
01645             ((similarity_traits & UpdatePixelTrait) == 0))
01646           continue;
01647         SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
01648           QuantumRange*similarity),q);
01649       }
01650       q+=GetPixelChannels(similarity_image);
01651     }
01652     if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
01653       status=MagickFalse;
01654     if (image->progress_monitor != (MagickProgressMonitor) NULL)
01655       {
01656         MagickBooleanType
01657           proceed;
01658 
01659 #if defined(MAGICKCORE_OPENMP_SUPPORT)
01660   #pragma omp critical (MagickCore_SimilarityImage)
01661 #endif
01662         proceed=SetImageProgress(image,SimilarityImageTag,progress++,
01663           image->rows);
01664         if (proceed == MagickFalse)
01665           status=MagickFalse;
01666       }
01667   }
01668   similarity_view=DestroyCacheView(similarity_view);
01669   return(similarity_image);
01670 }