Home | History | Annotate | Download | only in MagickCore
      1 /*
      2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      3 %                                                                             %
      4 %                                                                             %
      5 %                                                                             %
      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               %
     11 %                                                                             %
     12 %                                                                             %
     13 %                      MagickCore Image Comparison Methods                    %
     14 %                                                                             %
     15 %                              Software Design                                %
     16 %                                  Cristy                                     %
     17 %                               December 2003                                 %
     18 %                                                                             %
     19 %                                                                             %
     20 %  Copyright 1999-2016 ImageMagick Studio LLC, a non-profit organization      %
     21 %  dedicated to making software imaging solutions freely available.           %
     22 %                                                                             %
     23 %  You may not use this file except in compliance with the License.  You may  %
     24 %  obtain a copy of the License at                                            %
     25 %                                                                             %
     26 %    http://www.imagemagick.org/script/license.php                            %
     27 %                                                                             %
     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.                                             %
     33 %                                                                             %
     34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
     35 %
     36 %
     37 %
     38 */
     39 
     40 /*
     42   Include declarations.
     43 */
     44 #include "MagickCore/studio.h"
     45 #include "MagickCore/artifact.h"
     46 #include "MagickCore/attribute.h"
     47 #include "MagickCore/cache-view.h"
     48 #include "MagickCore/channel.h"
     49 #include "MagickCore/client.h"
     50 #include "MagickCore/color.h"
     51 #include "MagickCore/color-private.h"
     52 #include "MagickCore/colorspace.h"
     53 #include "MagickCore/colorspace-private.h"
     54 #include "MagickCore/compare.h"
     55 #include "MagickCore/composite-private.h"
     56 #include "MagickCore/constitute.h"
     57 #include "MagickCore/exception-private.h"
     58 #include "MagickCore/geometry.h"
     59 #include "MagickCore/image-private.h"
     60 #include "MagickCore/list.h"
     61 #include "MagickCore/log.h"
     62 #include "MagickCore/memory_.h"
     63 #include "MagickCore/monitor.h"
     64 #include "MagickCore/monitor-private.h"
     65 #include "MagickCore/option.h"
     66 #include "MagickCore/pixel-accessor.h"
     67 #include "MagickCore/property.h"
     68 #include "MagickCore/resource_.h"
     69 #include "MagickCore/string_.h"
     70 #include "MagickCore/statistic.h"
     71 #include "MagickCore/thread-private.h"
     72 #include "MagickCore/transform.h"
     73 #include "MagickCore/utility.h"
     74 #include "MagickCore/version.h"
     75 
     76 /*
     78 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
     79 %                                                                             %
     80 %                                                                             %
     81 %                                                                             %
     82 %   C o m p a r e I m a g e                                                   %
     83 %                                                                             %
     84 %                                                                             %
     85 %                                                                             %
     86 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
     87 %
     88 %  CompareImages() compares one or more pixel channels of an image to a
     89 %  reconstructed image and returns the difference image.
     90 %
     91 %  The format of the CompareImages method is:
     92 %
     93 %      Image *CompareImages(const Image *image,const Image *reconstruct_image,
     94 %        const MetricType metric,double *distortion,ExceptionInfo *exception)
     95 %
     96 %  A description of each parameter follows:
     97 %
     98 %    o image: the image.
     99 %
    100 %    o reconstruct_image: the reconstruct image.
    101 %
    102 %    o metric: the metric.
    103 %
    104 %    o distortion: the computed distortion between the images.
    105 %
    106 %    o exception: return any errors or warnings in this structure.
    107 %
    108 */
    109 
    110 static size_t GetImageChannels(const Image *image)
    111 {
    112   register ssize_t
    113     i;
    114 
    115   size_t
    116     channels;
    117 
    118   channels=0;
    119   for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
    120   {
    121     PixelChannel channel=GetPixelChannelChannel(image,i);
    122     PixelTrait traits=GetPixelChannelTraits(image,channel);
    123     if ((traits & UpdatePixelTrait) != 0)
    124       channels++;
    125   }
    126   return(channels == 0 ? (size_t) 1 : channels);
    127 }
    128 
    129 MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
    130   const MetricType metric,double *distortion,ExceptionInfo *exception)
    131 {
    132   CacheView
    133     *highlight_view,
    134     *image_view,
    135     *reconstruct_view;
    136 
    137   double
    138     fuzz;
    139 
    140   const char
    141     *artifact;
    142 
    143   Image
    144     *difference_image,
    145     *highlight_image;
    146 
    147   MagickBooleanType
    148     status;
    149 
    150   PixelInfo
    151     highlight,
    152     lowlight;
    153 
    154   RectangleInfo
    155     geometry;
    156 
    157   size_t
    158     columns,
    159     rows;
    160 
    161   ssize_t
    162     y;
    163 
    164   assert(image != (Image *) NULL);
    165   assert(image->signature == MagickCoreSignature);
    166   if (image->debug != MagickFalse)
    167     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
    168   assert(reconstruct_image != (const Image *) NULL);
    169   assert(reconstruct_image->signature == MagickCoreSignature);
    170   assert(distortion != (double *) NULL);
    171   *distortion=0.0;
    172   if (image->debug != MagickFalse)
    173     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
    174   status=GetImageDistortion(image,reconstruct_image,metric,distortion,
    175     exception);
    176   if (status == MagickFalse)
    177     return((Image *) NULL);
    178   columns=MagickMax(image->columns,reconstruct_image->columns);
    179   rows=MagickMax(image->rows,reconstruct_image->rows);
    180   SetGeometry(image,&geometry);
    181   geometry.width=columns;
    182   geometry.height=rows;
    183   difference_image=ExtentImage(image,&geometry,exception);
    184   if (difference_image == (Image *) NULL)
    185     return((Image *) NULL);
    186   (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception);
    187   highlight_image=CloneImage(image,columns,rows,MagickTrue,exception);
    188   if (highlight_image == (Image *) NULL)
    189     {
    190       difference_image=DestroyImage(difference_image);
    191       return((Image *) NULL);
    192     }
    193   status=SetImageStorageClass(highlight_image,DirectClass,exception);
    194   if (status == MagickFalse)
    195     {
    196       difference_image=DestroyImage(difference_image);
    197       highlight_image=DestroyImage(highlight_image);
    198       return((Image *) NULL);
    199     }
    200   (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception);
    201   (void) QueryColorCompliance("#f1001ecc",AllCompliance,&highlight,exception);
    202   artifact=GetImageArtifact(image,"highlight-color");
    203   if (artifact != (const char *) NULL)
    204     (void) QueryColorCompliance(artifact,AllCompliance,&highlight,exception);
    205   (void) QueryColorCompliance("#ffffffcc",AllCompliance,&lowlight,exception);
    206   artifact=GetImageArtifact(image,"lowlight-color");
    207   if (artifact != (const char *) NULL)
    208     (void) QueryColorCompliance(artifact,AllCompliance,&lowlight,exception);
    209   /*
    210     Generate difference image.
    211   */
    212   status=MagickTrue;
    213   fuzz=GetFuzzyColorDistance(image,reconstruct_image);
    214   image_view=AcquireVirtualCacheView(image,exception);
    215   reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
    216   highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
    217 #if defined(MAGICKCORE_OPENMP_SUPPORT)
    218   #pragma omp parallel for schedule(static,4) shared(status) \
    219     magick_threads(image,highlight_image,rows,1)
    220 #endif
    221   for (y=0; y < (ssize_t) rows; y++)
    222   {
    223     MagickBooleanType
    224       sync;
    225 
    226     register const Quantum
    227       *magick_restrict p,
    228       *magick_restrict q;
    229 
    230     register Quantum
    231       *magick_restrict r;
    232 
    233     register ssize_t
    234       x;
    235 
    236     if (status == MagickFalse)
    237       continue;
    238     p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
    239     q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
    240     r=QueueCacheViewAuthenticPixels(highlight_view,0,y,columns,1,exception);
    241     if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
    242         (r == (Quantum *) NULL))
    243       {
    244         status=MagickFalse;
    245         continue;
    246       }
    247     for (x=0; x < (ssize_t) columns; x++)
    248     {
    249       double
    250         Da,
    251         Sa;
    252 
    253       MagickStatusType
    254         difference;
    255 
    256       register ssize_t
    257         i;
    258 
    259       if (GetPixelReadMask(image,p) == 0)
    260         {
    261           SetPixelViaPixelInfo(highlight_image,&lowlight,r);
    262           p+=GetPixelChannels(image);
    263           q+=GetPixelChannels(reconstruct_image);
    264           r+=GetPixelChannels(highlight_image);
    265           continue;
    266         }
    267       difference=MagickFalse;
    268       Sa=QuantumScale*GetPixelAlpha(image,p);
    269       Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
    270       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
    271       {
    272         double
    273           distance;
    274 
    275         PixelChannel channel=GetPixelChannelChannel(image,i);
    276         PixelTrait traits=GetPixelChannelTraits(image,channel);
    277         PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
    278           channel);
    279         if ((traits == UndefinedPixelTrait) ||
    280             (reconstruct_traits == UndefinedPixelTrait) ||
    281             ((reconstruct_traits & UpdatePixelTrait) == 0))
    282           continue;
    283         distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
    284         if ((distance*distance) > fuzz)
    285           {
    286             difference=MagickTrue;
    287             break;
    288           }
    289       }
    290       if (difference == MagickFalse)
    291         SetPixelViaPixelInfo(highlight_image,&lowlight,r);
    292       else
    293         SetPixelViaPixelInfo(highlight_image,&highlight,r);
    294       p+=GetPixelChannels(image);
    295       q+=GetPixelChannels(reconstruct_image);
    296       r+=GetPixelChannels(highlight_image);
    297     }
    298     sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
    299     if (sync == MagickFalse)
    300       status=MagickFalse;
    301   }
    302   highlight_view=DestroyCacheView(highlight_view);
    303   reconstruct_view=DestroyCacheView(reconstruct_view);
    304   image_view=DestroyCacheView(image_view);
    305   (void) CompositeImage(difference_image,highlight_image,image->compose,
    306     MagickTrue,0,0,exception);
    307   (void) SetImageAlphaChannel(difference_image,OffAlphaChannel,exception);
    308   highlight_image=DestroyImage(highlight_image);
    309   if (status == MagickFalse)
    310     difference_image=DestroyImage(difference_image);
    311   return(difference_image);
    312 }
    313 
    314 /*
    316 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    317 %                                                                             %
    318 %                                                                             %
    319 %                                                                             %
    320 %   G e t I m a g e D i s t o r t i o n                                       %
    321 %                                                                             %
    322 %                                                                             %
    323 %                                                                             %
    324 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    325 %
    326 %  GetImageDistortion() compares one or more pixel channels of an image to a
    327 %  reconstructed image and returns the specified distortion metric.
    328 %
    329 %  The format of the GetImageDistortion method is:
    330 %
    331 %      MagickBooleanType GetImageDistortion(const Image *image,
    332 %        const Image *reconstruct_image,const MetricType metric,
    333 %        double *distortion,ExceptionInfo *exception)
    334 %
    335 %  A description of each parameter follows:
    336 %
    337 %    o image: the image.
    338 %
    339 %    o reconstruct_image: the reconstruct image.
    340 %
    341 %    o metric: the metric.
    342 %
    343 %    o distortion: the computed distortion between the images.
    344 %
    345 %    o exception: return any errors or warnings in this structure.
    346 %
    347 */
    348 
    349 static MagickBooleanType GetAbsoluteDistortion(const Image *image,
    350   const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
    351 {
    352   CacheView
    353     *image_view,
    354     *reconstruct_view;
    355 
    356   double
    357     fuzz;
    358 
    359   MagickBooleanType
    360     status;
    361 
    362   size_t
    363     columns,
    364     rows;
    365 
    366   ssize_t
    367     y;
    368 
    369   /*
    370     Compute the absolute difference in pixels between two images.
    371   */
    372   status=MagickTrue;
    373   fuzz=GetFuzzyColorDistance(image,reconstruct_image);
    374   rows=MagickMax(image->rows,reconstruct_image->rows);
    375   columns=MagickMax(image->columns,reconstruct_image->columns);
    376   image_view=AcquireVirtualCacheView(image,exception);
    377   reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
    378 #if defined(MAGICKCORE_OPENMP_SUPPORT)
    379   #pragma omp parallel for schedule(static,4) shared(status) \
    380     magick_threads(image,image,rows,1)
    381 #endif
    382   for (y=0; y < (ssize_t) rows; y++)
    383   {
    384     double
    385       channel_distortion[MaxPixelChannels+1];
    386 
    387     register const Quantum
    388       *magick_restrict p,
    389       *magick_restrict q;
    390 
    391     register ssize_t
    392       j,
    393       x;
    394 
    395     if (status == MagickFalse)
    396       continue;
    397     p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
    398     q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
    399     if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
    400       {
    401         status=MagickFalse;
    402         continue;
    403       }
    404     (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
    405     for (x=0; x < (ssize_t) columns; x++)
    406     {
    407       double
    408         Da,
    409         Sa;
    410 
    411       MagickBooleanType
    412         difference;
    413 
    414       register ssize_t
    415         i;
    416 
    417       if (GetPixelReadMask(image,p) == 0)
    418         {
    419           p+=GetPixelChannels(image);
    420           q+=GetPixelChannels(reconstruct_image);
    421           continue;
    422         }
    423       difference=MagickFalse;
    424       Sa=QuantumScale*GetPixelAlpha(image,p);
    425       Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
    426       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
    427       {
    428         double
    429           distance;
    430 
    431         PixelChannel channel=GetPixelChannelChannel(image,i);
    432         PixelTrait traits=GetPixelChannelTraits(image,channel);
    433         PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
    434           channel);
    435         if ((traits == UndefinedPixelTrait) ||
    436             (reconstruct_traits == UndefinedPixelTrait) ||
    437             ((reconstruct_traits & UpdatePixelTrait) == 0))
    438           continue;
    439         distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
    440         if ((distance*distance) > fuzz)
    441           {
    442             channel_distortion[i]++;
    443             difference=MagickTrue;
    444           }
    445       }
    446       if (difference != MagickFalse)
    447         channel_distortion[CompositePixelChannel]++;
    448       p+=GetPixelChannels(image);
    449       q+=GetPixelChannels(reconstruct_image);
    450     }
    451 #if defined(MAGICKCORE_OPENMP_SUPPORT)
    452     #pragma omp critical (MagickCore_GetAbsoluteError)
    453 #endif
    454     for (j=0; j <= MaxPixelChannels; j++)
    455       distortion[j]+=channel_distortion[j];
    456   }
    457   reconstruct_view=DestroyCacheView(reconstruct_view);
    458   image_view=DestroyCacheView(image_view);
    459   return(status);
    460 }
    461 
    462 static MagickBooleanType GetFuzzDistortion(const Image *image,
    463   const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
    464 {
    465   CacheView
    466     *image_view,
    467     *reconstruct_view;
    468 
    469   MagickBooleanType
    470     status;
    471 
    472   register ssize_t
    473     j;
    474 
    475   size_t
    476     columns,
    477     rows;
    478 
    479   ssize_t
    480     y;
    481 
    482   status=MagickTrue;
    483   rows=MagickMax(image->rows,reconstruct_image->rows);
    484   columns=MagickMax(image->columns,reconstruct_image->columns);
    485   image_view=AcquireVirtualCacheView(image,exception);
    486   reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
    487 #if defined(MAGICKCORE_OPENMP_SUPPORT)
    488   #pragma omp parallel for schedule(static,4) shared(status) \
    489     magick_threads(image,image,rows,1)
    490 #endif
    491   for (y=0; y < (ssize_t) rows; y++)
    492   {
    493     double
    494       channel_distortion[MaxPixelChannels+1];
    495 
    496     register const Quantum
    497       *magick_restrict p,
    498       *magick_restrict q;
    499 
    500     register ssize_t
    501       x;
    502 
    503     if (status == MagickFalse)
    504       continue;
    505     p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
    506     q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
    507     if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
    508       {
    509         status=MagickFalse;
    510         continue;
    511       }
    512     (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
    513     for (x=0; x < (ssize_t) columns; x++)
    514     {
    515       double
    516         Da,
    517         Sa;
    518 
    519       register ssize_t
    520         i;
    521 
    522       if (GetPixelReadMask(image,p) == 0)
    523         {
    524           p+=GetPixelChannels(image);
    525           q+=GetPixelChannels(reconstruct_image);
    526           continue;
    527         }
    528       Sa=QuantumScale*GetPixelAlpha(image,p);
    529       Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
    530       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
    531       {
    532         double
    533           distance;
    534 
    535         PixelChannel channel=GetPixelChannelChannel(image,i);
    536         PixelTrait traits=GetPixelChannelTraits(image,channel);
    537         PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
    538           channel);
    539         if ((traits == UndefinedPixelTrait) ||
    540             (reconstruct_traits == UndefinedPixelTrait) ||
    541             ((reconstruct_traits & UpdatePixelTrait) == 0))
    542           continue;
    543         distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
    544           channel,q));
    545         channel_distortion[i]+=distance*distance;
    546         channel_distortion[CompositePixelChannel]+=distance*distance;
    547       }
    548       p+=GetPixelChannels(image);
    549       q+=GetPixelChannels(reconstruct_image);
    550     }
    551 #if defined(MAGICKCORE_OPENMP_SUPPORT)
    552     #pragma omp critical (MagickCore_GetFuzzDistortion)
    553 #endif
    554     for (j=0; j <= MaxPixelChannels; j++)
    555       distortion[j]+=channel_distortion[j];
    556   }
    557   reconstruct_view=DestroyCacheView(reconstruct_view);
    558   image_view=DestroyCacheView(image_view);
    559   for (j=0; j <= MaxPixelChannels; j++)
    560     distortion[j]/=((double) columns*rows);
    561   distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
    562   distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
    563   return(status);
    564 }
    565 
    566 static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
    567   const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
    568 {
    569   CacheView
    570     *image_view,
    571     *reconstruct_view;
    572 
    573   MagickBooleanType
    574     status;
    575 
    576   register ssize_t
    577     j;
    578 
    579   size_t
    580     columns,
    581     rows;
    582 
    583   ssize_t
    584     y;
    585 
    586   status=MagickTrue;
    587   rows=MagickMax(image->rows,reconstruct_image->rows);
    588   columns=MagickMax(image->columns,reconstruct_image->columns);
    589   image_view=AcquireVirtualCacheView(image,exception);
    590   reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
    591 #if defined(MAGICKCORE_OPENMP_SUPPORT)
    592   #pragma omp parallel for schedule(static,4) shared(status) \
    593     magick_threads(image,image,rows,1)
    594 #endif
    595   for (y=0; y < (ssize_t) rows; y++)
    596   {
    597     double
    598       channel_distortion[MaxPixelChannels+1];
    599 
    600     register const Quantum
    601       *magick_restrict p,
    602       *magick_restrict q;
    603 
    604     register ssize_t
    605       x;
    606 
    607     if (status == MagickFalse)
    608       continue;
    609     p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
    610     q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
    611     if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
    612       {
    613         status=MagickFalse;
    614         continue;
    615       }
    616     (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
    617     for (x=0; x < (ssize_t) columns; x++)
    618     {
    619       double
    620         Da,
    621         Sa;
    622 
    623       register ssize_t
    624         i;
    625 
    626       if (GetPixelReadMask(image,p) == 0)
    627         {
    628           p+=GetPixelChannels(image);
    629           q+=GetPixelChannels(reconstruct_image);
    630           continue;
    631         }
    632       Sa=QuantumScale*GetPixelAlpha(image,p);
    633       Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
    634       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
    635       {
    636         double
    637           distance;
    638 
    639         PixelChannel channel=GetPixelChannelChannel(image,i);
    640         PixelTrait traits=GetPixelChannelTraits(image,channel);
    641         PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
    642           channel);
    643         if ((traits == UndefinedPixelTrait) ||
    644             (reconstruct_traits == UndefinedPixelTrait) ||
    645             ((reconstruct_traits & UpdatePixelTrait) == 0))
    646           continue;
    647         distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
    648           channel,q));
    649         channel_distortion[i]+=distance;
    650         channel_distortion[CompositePixelChannel]+=distance;
    651       }
    652       p+=GetPixelChannels(image);
    653       q+=GetPixelChannels(reconstruct_image);
    654     }
    655 #if defined(MAGICKCORE_OPENMP_SUPPORT)
    656     #pragma omp critical (MagickCore_GetMeanAbsoluteError)
    657 #endif
    658     for (j=0; j <= MaxPixelChannels; j++)
    659       distortion[j]+=channel_distortion[j];
    660   }
    661   reconstruct_view=DestroyCacheView(reconstruct_view);
    662   image_view=DestroyCacheView(image_view);
    663   for (j=0; j <= MaxPixelChannels; j++)
    664     distortion[j]/=((double) columns*rows);
    665   distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
    666   return(status);
    667 }
    668 
    669 static MagickBooleanType GetMeanErrorPerPixel(Image *image,
    670   const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
    671 {
    672   CacheView
    673     *image_view,
    674     *reconstruct_view;
    675 
    676   MagickBooleanType
    677     status;
    678 
    679   double
    680     area,
    681     maximum_error,
    682     mean_error;
    683 
    684   size_t
    685     columns,
    686     rows;
    687 
    688   ssize_t
    689     y;
    690 
    691   status=MagickTrue;
    692   area=0.0;
    693   maximum_error=0.0;
    694   mean_error=0.0;
    695   rows=MagickMax(image->rows,reconstruct_image->rows);
    696   columns=MagickMax(image->columns,reconstruct_image->columns);
    697   image_view=AcquireVirtualCacheView(image,exception);
    698   reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
    699   for (y=0; y < (ssize_t) rows; y++)
    700   {
    701     register const Quantum
    702       *magick_restrict p,
    703       *magick_restrict q;
    704 
    705     register ssize_t
    706       x;
    707 
    708     p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
    709     q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
    710     if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
    711       {
    712         status=MagickFalse;
    713         break;
    714       }
    715     for (x=0; x < (ssize_t) columns; x++)
    716     {
    717       double
    718         Da,
    719         Sa;
    720 
    721       register ssize_t
    722         i;
    723 
    724       if (GetPixelReadMask(image,p) == 0)
    725         {
    726           p+=GetPixelChannels(image);
    727           q+=GetPixelChannels(reconstruct_image);
    728           continue;
    729         }
    730       Sa=QuantumScale*GetPixelAlpha(image,p);
    731       Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
    732       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
    733       {
    734         double
    735           distance;
    736 
    737         PixelChannel channel=GetPixelChannelChannel(image,i);
    738         PixelTrait traits=GetPixelChannelTraits(image,channel);
    739         PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
    740           channel);
    741         if ((traits == UndefinedPixelTrait) ||
    742             (reconstruct_traits == UndefinedPixelTrait) ||
    743             ((reconstruct_traits & UpdatePixelTrait) == 0))
    744           continue;
    745         distance=fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q));
    746         distortion[i]+=distance;
    747         distortion[CompositePixelChannel]+=distance;
    748         mean_error+=distance*distance;
    749         if (distance > maximum_error)
    750           maximum_error=distance;
    751         area++;
    752       }
    753       p+=GetPixelChannels(image);
    754       q+=GetPixelChannels(reconstruct_image);
    755     }
    756   }
    757   reconstruct_view=DestroyCacheView(reconstruct_view);
    758   image_view=DestroyCacheView(image_view);
    759   image->error.mean_error_per_pixel=distortion[CompositePixelChannel]/area;
    760   image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
    761   image->error.normalized_maximum_error=QuantumScale*maximum_error;
    762   return(status);
    763 }
    764 
    765 static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
    766   const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
    767 {
    768   CacheView
    769     *image_view,
    770     *reconstruct_view;
    771 
    772   MagickBooleanType
    773     status;
    774 
    775   register ssize_t
    776     j;
    777 
    778   size_t
    779     columns,
    780     rows;
    781 
    782   ssize_t
    783     y;
    784 
    785   status=MagickTrue;
    786   rows=MagickMax(image->rows,reconstruct_image->rows);
    787   columns=MagickMax(image->columns,reconstruct_image->columns);
    788   image_view=AcquireVirtualCacheView(image,exception);
    789   reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
    790 #if defined(MAGICKCORE_OPENMP_SUPPORT)
    791   #pragma omp parallel for schedule(static,4) shared(status) \
    792     magick_threads(image,image,rows,1)
    793 #endif
    794   for (y=0; y < (ssize_t) rows; y++)
    795   {
    796     double
    797       channel_distortion[MaxPixelChannels+1];
    798 
    799     register const Quantum
    800       *magick_restrict p,
    801       *magick_restrict q;
    802 
    803     register ssize_t
    804       x;
    805 
    806     if (status == MagickFalse)
    807       continue;
    808     p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
    809     q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
    810     if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
    811       {
    812         status=MagickFalse;
    813         continue;
    814       }
    815     (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
    816     for (x=0; x < (ssize_t) columns; x++)
    817     {
    818       double
    819         Da,
    820         Sa;
    821 
    822       register ssize_t
    823         i;
    824 
    825       if (GetPixelReadMask(image,p) == 0)
    826         {
    827           p+=GetPixelChannels(image);
    828           q+=GetPixelChannels(reconstruct_image);
    829           continue;
    830         }
    831       Sa=QuantumScale*GetPixelAlpha(image,p);
    832       Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
    833       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
    834       {
    835         double
    836           distance;
    837 
    838         PixelChannel channel=GetPixelChannelChannel(image,i);
    839         PixelTrait traits=GetPixelChannelTraits(image,channel);
    840         PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
    841           channel);
    842         if ((traits == UndefinedPixelTrait) ||
    843             (reconstruct_traits == UndefinedPixelTrait) ||
    844             ((reconstruct_traits & UpdatePixelTrait) == 0))
    845           continue;
    846         distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
    847           channel,q));
    848         channel_distortion[i]+=distance*distance;
    849         channel_distortion[CompositePixelChannel]+=distance*distance;
    850       }
    851       p+=GetPixelChannels(image);
    852       q+=GetPixelChannels(reconstruct_image);
    853     }
    854 #if defined(MAGICKCORE_OPENMP_SUPPORT)
    855     #pragma omp critical (MagickCore_GetMeanSquaredError)
    856 #endif
    857     for (j=0; j <= MaxPixelChannels; j++)
    858       distortion[j]+=channel_distortion[j];
    859   }
    860   reconstruct_view=DestroyCacheView(reconstruct_view);
    861   image_view=DestroyCacheView(image_view);
    862   for (j=0; j <= MaxPixelChannels; j++)
    863     distortion[j]/=((double) columns*rows);
    864   distortion[CompositePixelChannel]/=GetImageChannels(image);
    865   return(status);
    866 }
    867 
    868 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
    869   const Image *image,const Image *reconstruct_image,double *distortion,
    870   ExceptionInfo *exception)
    871 {
    872 #define SimilarityImageTag  "Similarity/Image"
    873 
    874   CacheView
    875     *image_view,
    876     *reconstruct_view;
    877 
    878   ChannelStatistics
    879     *image_statistics,
    880     *reconstruct_statistics;
    881 
    882   double
    883     area;
    884 
    885   MagickBooleanType
    886     status;
    887 
    888   MagickOffsetType
    889     progress;
    890 
    891   register ssize_t
    892     i;
    893 
    894   size_t
    895     columns,
    896     rows;
    897 
    898   ssize_t
    899     y;
    900 
    901   /*
    902     Normalize to account for variation due to lighting and exposure condition.
    903   */
    904   image_statistics=GetImageStatistics(image,exception);
    905   reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
    906   if ((image_statistics == (ChannelStatistics *) NULL) ||
    907       (reconstruct_statistics == (ChannelStatistics *) NULL))
    908     {
    909       if (image_statistics != (ChannelStatistics *) NULL)
    910         image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
    911           image_statistics);
    912       if (reconstruct_statistics != (ChannelStatistics *) NULL)
    913         reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
    914           reconstruct_statistics);
    915       return(MagickFalse);
    916     }
    917   status=MagickTrue;
    918   progress=0;
    919   for (i=0; i <= MaxPixelChannels; i++)
    920     distortion[i]=0.0;
    921   rows=MagickMax(image->rows,reconstruct_image->rows);
    922   columns=MagickMax(image->columns,reconstruct_image->columns);
    923   area=1.0/((double) columns*rows);
    924   image_view=AcquireVirtualCacheView(image,exception);
    925   reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
    926   for (y=0; y < (ssize_t) rows; y++)
    927   {
    928     register const Quantum
    929       *magick_restrict p,
    930       *magick_restrict q;
    931 
    932     register ssize_t
    933       x;
    934 
    935     p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
    936     q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
    937     if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
    938       {
    939         status=MagickFalse;
    940         break;
    941       }
    942     for (x=0; x < (ssize_t) columns; x++)
    943     {
    944       double
    945         Da,
    946         Sa;
    947 
    948       if (GetPixelReadMask(image,p) == 0)
    949         {
    950           p+=GetPixelChannels(image);
    951           q+=GetPixelChannels(reconstruct_image);
    952           continue;
    953         }
    954       Sa=QuantumScale*(image->alpha_trait != UndefinedPixelTrait ?
    955         GetPixelAlpha(image,p) : OpaqueAlpha);
    956       Da=QuantumScale*(reconstruct_image->alpha_trait != UndefinedPixelTrait ?
    957         GetPixelAlpha(reconstruct_image,q) : OpaqueAlpha);
    958       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
    959       {
    960         PixelChannel channel=GetPixelChannelChannel(image,i);
    961         PixelTrait traits=GetPixelChannelTraits(image,channel);
    962         PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
    963           channel);
    964         if ((traits == UndefinedPixelTrait) ||
    965             (reconstruct_traits == UndefinedPixelTrait) ||
    966             ((reconstruct_traits & UpdatePixelTrait) == 0))
    967           continue;
    968         if (channel == AlphaPixelChannel)
    969           {
    970             distortion[i]+=area*QuantumScale*(p[i]-
    971               image_statistics[channel].mean)*(GetPixelChannel(
    972               reconstruct_image,channel,q)-
    973               reconstruct_statistics[channel].mean);
    974           }
    975         else
    976           {
    977             distortion[i]+=area*QuantumScale*(Sa*p[i]-
    978               image_statistics[channel].mean)*(Da*GetPixelChannel(
    979               reconstruct_image,channel,q)-
    980               reconstruct_statistics[channel].mean);
    981           }
    982       }
    983       p+=GetPixelChannels(image);
    984       q+=GetPixelChannels(reconstruct_image);
    985     }
    986     if (image->progress_monitor != (MagickProgressMonitor) NULL)
    987       {
    988         MagickBooleanType
    989           proceed;
    990 
    991         proceed=SetImageProgress(image,SimilarityImageTag,progress++,rows);
    992         if (proceed == MagickFalse)
    993           {
    994             status=MagickFalse;
    995             break;
    996           }
    997       }
    998   }
    999   reconstruct_view=DestroyCacheView(reconstruct_view);
   1000   image_view=DestroyCacheView(image_view);
   1001   /*
   1002     Divide by the standard deviation.
   1003   */
   1004   distortion[CompositePixelChannel]=0.0;
   1005   for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
   1006   {
   1007     double
   1008       gamma;
   1009 
   1010     PixelChannel channel=GetPixelChannelChannel(image,i);
   1011     gamma=image_statistics[channel].standard_deviation*
   1012       reconstruct_statistics[channel].standard_deviation;
   1013     gamma=PerceptibleReciprocal(gamma);
   1014     distortion[i]=QuantumRange*gamma*distortion[i];
   1015     distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
   1016   }
   1017   distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
   1018     GetImageChannels(image));
   1019   /*
   1020     Free resources.
   1021   */
   1022   reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
   1023     reconstruct_statistics);
   1024   image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
   1025     image_statistics);
   1026   return(status);
   1027 }
   1028 
   1029 static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
   1030   const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
   1031 {
   1032   CacheView
   1033     *image_view,
   1034     *reconstruct_view;
   1035 
   1036   MagickBooleanType
   1037     status;
   1038 
   1039   size_t
   1040     columns,
   1041     rows;
   1042 
   1043   ssize_t
   1044     y;
   1045 
   1046   status=MagickTrue;
   1047   rows=MagickMax(image->rows,reconstruct_image->rows);
   1048   columns=MagickMax(image->columns,reconstruct_image->columns);
   1049   image_view=AcquireVirtualCacheView(image,exception);
   1050   reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
   1051 #if defined(MAGICKCORE_OPENMP_SUPPORT)
   1052   #pragma omp parallel for schedule(static,4) shared(status) \
   1053     magick_threads(image,image,rows,1)
   1054 #endif
   1055   for (y=0; y < (ssize_t) rows; y++)
   1056   {
   1057     double
   1058       channel_distortion[MaxPixelChannels+1];
   1059 
   1060     register const Quantum
   1061       *magick_restrict p,
   1062       *magick_restrict q;
   1063 
   1064     register ssize_t
   1065       j,
   1066       x;
   1067 
   1068     if (status == MagickFalse)
   1069       continue;
   1070     p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
   1071     q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
   1072     if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
   1073       {
   1074         status=MagickFalse;
   1075         continue;
   1076       }
   1077     (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
   1078     for (x=0; x < (ssize_t) columns; x++)
   1079     {
   1080       double
   1081         Da,
   1082         Sa;
   1083 
   1084       register ssize_t
   1085         i;
   1086 
   1087       if (GetPixelReadMask(image,p) == 0)
   1088         {
   1089           p+=GetPixelChannels(image);
   1090           q+=GetPixelChannels(reconstruct_image);
   1091           continue;
   1092         }
   1093       Sa=QuantumScale*GetPixelAlpha(image,p);
   1094       Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
   1095       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
   1096       {
   1097         double
   1098           distance;
   1099 
   1100         PixelChannel channel=GetPixelChannelChannel(image,i);
   1101         PixelTrait traits=GetPixelChannelTraits(image,channel);
   1102         PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
   1103           channel);
   1104         if ((traits == UndefinedPixelTrait) ||
   1105             (reconstruct_traits == UndefinedPixelTrait) ||
   1106             ((reconstruct_traits & UpdatePixelTrait) == 0))
   1107           continue;
   1108         distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
   1109           channel,q));
   1110         if (distance > channel_distortion[i])
   1111           channel_distortion[i]=distance;
   1112         if (distance > channel_distortion[CompositePixelChannel])
   1113           channel_distortion[CompositePixelChannel]=distance;
   1114       }
   1115       p+=GetPixelChannels(image);
   1116       q+=GetPixelChannels(reconstruct_image);
   1117     }
   1118 #if defined(MAGICKCORE_OPENMP_SUPPORT)
   1119     #pragma omp critical (MagickCore_GetPeakAbsoluteError)
   1120 #endif
   1121     for (j=0; j <= MaxPixelChannels; j++)
   1122       if (channel_distortion[j] > distortion[j])
   1123         distortion[j]=channel_distortion[j];
   1124   }
   1125   reconstruct_view=DestroyCacheView(reconstruct_view);
   1126   image_view=DestroyCacheView(image_view);
   1127   return(status);
   1128 }
   1129 
   1130 static inline double MagickLog10(const double x)
   1131 {
   1132 #define Log10Epsilon  (1.0e-11)
   1133 
   1134  if (fabs(x) < Log10Epsilon)
   1135    return(log10(Log10Epsilon));
   1136  return(log10(fabs(x)));
   1137 }
   1138 
   1139 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
   1140   const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
   1141 {
   1142   MagickBooleanType
   1143     status;
   1144 
   1145   register ssize_t
   1146     i;
   1147 
   1148   status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
   1149   for (i=0; i <= MaxPixelChannels; i++)
   1150     distortion[i]=20.0*MagickLog10((double) 1.0/sqrt(distortion[i]));
   1151   return(status);
   1152 }
   1153 
   1154 static MagickBooleanType GetPerceptualHashDistortion(const Image *image,
   1155   const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
   1156 {
   1157   ChannelPerceptualHash
   1158     *image_phash,
   1159     *reconstruct_phash;
   1160 
   1161   ssize_t
   1162     channel;
   1163 
   1164   /*
   1165     Compute perceptual hash in the sRGB colorspace.
   1166   */
   1167   image_phash=GetImagePerceptualHash(image,exception);
   1168   if (image_phash == (ChannelPerceptualHash *) NULL)
   1169     return(MagickFalse);
   1170   reconstruct_phash=GetImagePerceptualHash(reconstruct_image,exception);
   1171   if (reconstruct_phash == (ChannelPerceptualHash *) NULL)
   1172     {
   1173       image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
   1174       return(MagickFalse);
   1175     }
   1176 #if defined(MAGICKCORE_OPENMP_SUPPORT)
   1177   #pragma omp parallel for schedule(static,4)
   1178 #endif
   1179   for (channel=0; channel < MaxPixelChannels; channel++)
   1180   {
   1181     double
   1182       difference;
   1183 
   1184     register ssize_t
   1185       i;
   1186 
   1187     difference=0.0;
   1188     for (i=0; i < MaximumNumberOfImageMoments; i++)
   1189     {
   1190       double
   1191         alpha,
   1192         beta;
   1193 
   1194       alpha=image_phash[channel].srgb_hu_phash[i];
   1195       beta=reconstruct_phash[channel].srgb_hu_phash[i];
   1196       difference+=(beta-alpha)*(beta-alpha);
   1197     }
   1198     distortion[channel]+=difference;
   1199 #if defined(MAGICKCORE_OPENMP_SUPPORT)
   1200     #pragma omp critical (MagickCore_GetPerceptualHashDistortion)
   1201 #endif
   1202     distortion[CompositePixelChannel]+=difference;
   1203   }
   1204   /*
   1205     Compute perceptual hash in the HCLP colorspace.
   1206   */
   1207 #if defined(MAGICKCORE_OPENMP_SUPPORT)
   1208   #pragma omp parallel for schedule(static,4)
   1209 #endif
   1210   for (channel=0; channel < MaxPixelChannels; channel++)
   1211   {
   1212     double
   1213       difference;
   1214 
   1215     register ssize_t
   1216       i;
   1217 
   1218     difference=0.0;
   1219     for (i=0; i < MaximumNumberOfImageMoments; i++)
   1220     {
   1221       double
   1222         alpha,
   1223         beta;
   1224 
   1225       alpha=image_phash[channel].hclp_hu_phash[i];
   1226       beta=reconstruct_phash[channel].hclp_hu_phash[i];
   1227       difference+=(beta-alpha)*(beta-alpha);
   1228     }
   1229     distortion[channel]+=difference;
   1230 #if defined(MAGICKCORE_OPENMP_SUPPORT)
   1231     #pragma omp critical (MagickCore_GetPerceptualHashDistortion)
   1232 #endif
   1233     distortion[CompositePixelChannel]+=difference;
   1234   }
   1235   /*
   1236     Free resources.
   1237   */
   1238   reconstruct_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
   1239     reconstruct_phash);
   1240   image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
   1241   return(MagickTrue);
   1242 }
   1243 
   1244 static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
   1245   const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
   1246 {
   1247   MagickBooleanType
   1248     status;
   1249 
   1250   register ssize_t
   1251     i;
   1252 
   1253   status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
   1254   for (i=0; i <= MaxPixelChannels; i++)
   1255     distortion[i]=sqrt(distortion[i]);
   1256   return(status);
   1257 }
   1258 
   1259 MagickExport MagickBooleanType GetImageDistortion(Image *image,
   1260   const Image *reconstruct_image,const MetricType metric,double *distortion,
   1261   ExceptionInfo *exception)
   1262 {
   1263   double
   1264     *channel_distortion;
   1265 
   1266   MagickBooleanType
   1267     status;
   1268 
   1269   size_t
   1270     length;
   1271 
   1272   assert(image != (Image *) NULL);
   1273   assert(image->signature == MagickCoreSignature);
   1274   if (image->debug != MagickFalse)
   1275     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
   1276   assert(reconstruct_image != (const Image *) NULL);
   1277   assert(reconstruct_image->signature == MagickCoreSignature);
   1278   assert(distortion != (double *) NULL);
   1279   *distortion=0.0;
   1280   if (image->debug != MagickFalse)
   1281     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
   1282   /*
   1283     Get image distortion.
   1284   */
   1285   length=MaxPixelChannels+1;
   1286   channel_distortion=(double *) AcquireQuantumMemory(length,
   1287     sizeof(*channel_distortion));
   1288   if (channel_distortion == (double *) NULL)
   1289     ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
   1290   (void) ResetMagickMemory(channel_distortion,0,length*
   1291     sizeof(*channel_distortion));
   1292   switch (metric)
   1293   {
   1294     case AbsoluteErrorMetric:
   1295     {
   1296       status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
   1297         exception);
   1298       break;
   1299     }
   1300     case FuzzErrorMetric:
   1301     {
   1302       status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
   1303         exception);
   1304       break;
   1305     }
   1306     case MeanAbsoluteErrorMetric:
   1307     {
   1308       status=GetMeanAbsoluteDistortion(image,reconstruct_image,
   1309         channel_distortion,exception);
   1310       break;
   1311     }
   1312     case MeanErrorPerPixelErrorMetric:
   1313     {
   1314       status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
   1315         exception);
   1316       break;
   1317     }
   1318     case MeanSquaredErrorMetric:
   1319     {
   1320       status=GetMeanSquaredDistortion(image,reconstruct_image,
   1321         channel_distortion,exception);
   1322       break;
   1323     }
   1324     case NormalizedCrossCorrelationErrorMetric:
   1325     default:
   1326     {
   1327       status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
   1328         channel_distortion,exception);
   1329       break;
   1330     }
   1331     case PeakAbsoluteErrorMetric:
   1332     {
   1333       status=GetPeakAbsoluteDistortion(image,reconstruct_image,
   1334         channel_distortion,exception);
   1335       break;
   1336     }
   1337     case PeakSignalToNoiseRatioErrorMetric:
   1338     {
   1339       status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
   1340         channel_distortion,exception);
   1341       break;
   1342     }
   1343     case PerceptualHashErrorMetric:
   1344     {
   1345       status=GetPerceptualHashDistortion(image,reconstruct_image,
   1346         channel_distortion,exception);
   1347       break;
   1348     }
   1349     case RootMeanSquaredErrorMetric:
   1350     {
   1351       status=GetRootMeanSquaredDistortion(image,reconstruct_image,
   1352         channel_distortion,exception);
   1353       break;
   1354     }
   1355   }
   1356   *distortion=channel_distortion[CompositePixelChannel];
   1357   channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
   1358   (void) FormatImageProperty(image,"distortion","%.*g",GetMagickPrecision(),
   1359     *distortion);
   1360   return(status);
   1361 }
   1362 
   1363 /*
   1365 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   1366 %                                                                             %
   1367 %                                                                             %
   1368 %                                                                             %
   1369 %   G e t I m a g e D i s t o r t i o n s                                     %
   1370 %                                                                             %
   1371 %                                                                             %
   1372 %                                                                             %
   1373 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   1374 %
   1375 %  GetImageDistortions() compares the pixel channels of an image to a
   1376 %  reconstructed image and returns the specified distortion metric for each
   1377 %  channel.
   1378 %
   1379 %  The format of the GetImageDistortions method is:
   1380 %
   1381 %      double *GetImageDistortions(const Image *image,
   1382 %        const Image *reconstruct_image,const MetricType metric,
   1383 %        ExceptionInfo *exception)
   1384 %
   1385 %  A description of each parameter follows:
   1386 %
   1387 %    o image: the image.
   1388 %
   1389 %    o reconstruct_image: the reconstruct image.
   1390 %
   1391 %    o metric: the metric.
   1392 %
   1393 %    o exception: return any errors or warnings in this structure.
   1394 %
   1395 */
   1396 MagickExport double *GetImageDistortions(Image *image,
   1397   const Image *reconstruct_image,const MetricType metric,
   1398   ExceptionInfo *exception)
   1399 {
   1400   double
   1401     *channel_distortion;
   1402 
   1403   MagickBooleanType
   1404     status;
   1405 
   1406   size_t
   1407     length;
   1408 
   1409   assert(image != (Image *) NULL);
   1410   assert(image->signature == MagickCoreSignature);
   1411   if (image->debug != MagickFalse)
   1412     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
   1413   assert(reconstruct_image != (const Image *) NULL);
   1414   assert(reconstruct_image->signature == MagickCoreSignature);
   1415   if (image->debug != MagickFalse)
   1416     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
   1417   /*
   1418     Get image distortion.
   1419   */
   1420   length=MaxPixelChannels+1UL;
   1421   channel_distortion=(double *) AcquireQuantumMemory(length,
   1422     sizeof(*channel_distortion));
   1423   if (channel_distortion == (double *) NULL)
   1424     ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
   1425   (void) ResetMagickMemory(channel_distortion,0,length*
   1426     sizeof(*channel_distortion));
   1427   status=MagickTrue;
   1428   switch (metric)
   1429   {
   1430     case AbsoluteErrorMetric:
   1431     {
   1432       status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
   1433         exception);
   1434       break;
   1435     }
   1436     case FuzzErrorMetric:
   1437     {
   1438       status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
   1439         exception);
   1440       break;
   1441     }
   1442     case MeanAbsoluteErrorMetric:
   1443     {
   1444       status=GetMeanAbsoluteDistortion(image,reconstruct_image,
   1445         channel_distortion,exception);
   1446       break;
   1447     }
   1448     case MeanErrorPerPixelErrorMetric:
   1449     {
   1450       status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
   1451         exception);
   1452       break;
   1453     }
   1454     case MeanSquaredErrorMetric:
   1455     {
   1456       status=GetMeanSquaredDistortion(image,reconstruct_image,
   1457         channel_distortion,exception);
   1458       break;
   1459     }
   1460     case NormalizedCrossCorrelationErrorMetric:
   1461     default:
   1462     {
   1463       status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
   1464         channel_distortion,exception);
   1465       break;
   1466     }
   1467     case PeakAbsoluteErrorMetric:
   1468     {
   1469       status=GetPeakAbsoluteDistortion(image,reconstruct_image,
   1470         channel_distortion,exception);
   1471       break;
   1472     }
   1473     case PeakSignalToNoiseRatioErrorMetric:
   1474     {
   1475       status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
   1476         channel_distortion,exception);
   1477       break;
   1478     }
   1479     case PerceptualHashErrorMetric:
   1480     {
   1481       status=GetRootMeanSquaredDistortion(image,reconstruct_image,
   1482         channel_distortion,exception);
   1483       break;
   1484     }
   1485     case RootMeanSquaredErrorMetric:
   1486     {
   1487       status=GetRootMeanSquaredDistortion(image,reconstruct_image,
   1488         channel_distortion,exception);
   1489       break;
   1490     }
   1491   }
   1492   if (status == MagickFalse)
   1493     {
   1494       channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
   1495       return((double *) NULL);
   1496     }
   1497   return(channel_distortion);
   1498 }
   1499 
   1500 /*
   1502 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   1503 %                                                                             %
   1504 %                                                                             %
   1505 %                                                                             %
   1506 %  I s I m a g e s E q u a l                                                  %
   1507 %                                                                             %
   1508 %                                                                             %
   1509 %                                                                             %
   1510 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   1511 %
   1512 %  IsImagesEqual() compare the pixels of two images and returns immediately
   1513 %  if any pixel is not identical.
   1514 %
   1515 %  The format of the IsImagesEqual method is:
   1516 %
   1517 %      MagickBooleanType IsImagesEqual(const Image *image,
   1518 %        const Image *reconstruct_image,ExceptionInfo *exception)
   1519 %
   1520 %  A description of each parameter follows.
   1521 %
   1522 %    o image: the image.
   1523 %
   1524 %    o reconstruct_image: the reconstruct image.
   1525 %
   1526 %    o exception: return any errors or warnings in this structure.
   1527 %
   1528 */
   1529 MagickExport MagickBooleanType IsImagesEqual(const Image *image,
   1530   const Image *reconstruct_image,ExceptionInfo *exception)
   1531 {
   1532   CacheView
   1533     *image_view,
   1534     *reconstruct_view;
   1535 
   1536   size_t
   1537     columns,
   1538     rows;
   1539 
   1540   ssize_t
   1541     y;
   1542 
   1543   assert(image != (Image *) NULL);
   1544   assert(image->signature == MagickCoreSignature);
   1545   assert(reconstruct_image != (const Image *) NULL);
   1546   assert(reconstruct_image->signature == MagickCoreSignature);
   1547   rows=MagickMax(image->rows,reconstruct_image->rows);
   1548   columns=MagickMax(image->columns,reconstruct_image->columns);
   1549   image_view=AcquireVirtualCacheView(image,exception);
   1550   reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
   1551   for (y=0; y < (ssize_t) rows; y++)
   1552   {
   1553     register const Quantum
   1554       *magick_restrict p,
   1555       *magick_restrict q;
   1556 
   1557     register ssize_t
   1558       x;
   1559 
   1560     p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
   1561     q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
   1562     if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
   1563       break;
   1564     for (x=0; x < (ssize_t) columns; x++)
   1565     {
   1566       register ssize_t
   1567         i;
   1568 
   1569       if (GetPixelReadMask(image,p) == 0)
   1570         {
   1571           p+=GetPixelChannels(image);
   1572           q+=GetPixelChannels(reconstruct_image);
   1573           continue;
   1574         }
   1575       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
   1576       {
   1577         double
   1578           distance;
   1579 
   1580         PixelChannel channel=GetPixelChannelChannel(image,i);
   1581         PixelTrait traits=GetPixelChannelTraits(image,channel);
   1582         PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
   1583           channel);
   1584         if ((traits == UndefinedPixelTrait) ||
   1585             (reconstruct_traits == UndefinedPixelTrait) ||
   1586             ((reconstruct_traits & UpdatePixelTrait) == 0))
   1587           continue;
   1588         distance=fabs(p[i]-(double) GetPixelChannel(reconstruct_image,
   1589           channel,q));
   1590         if (distance >= MagickEpsilon)
   1591           break;
   1592       }
   1593       if (i < (ssize_t) GetPixelChannels(image))
   1594         break;
   1595       p+=GetPixelChannels(image);
   1596       q+=GetPixelChannels(reconstruct_image);
   1597     }
   1598     if (x < (ssize_t) columns)
   1599       break;
   1600   }
   1601   reconstruct_view=DestroyCacheView(reconstruct_view);
   1602   image_view=DestroyCacheView(image_view);
   1603   return(y < (ssize_t) rows ? MagickFalse : MagickTrue);
   1604 }
   1605 
   1606 /*
   1608 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   1609 %                                                                             %
   1610 %                                                                             %
   1611 %                                                                             %
   1612 %  S e t I m a g e C o l o r M e t r i c                                      %
   1613 %                                                                             %
   1614 %                                                                             %
   1615 %                                                                             %
   1616 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   1617 %
   1618 %  SetImageColorMetric() measures the difference between colors at each pixel
   1619 %  location of two images.  A value other than 0 means the colors match
   1620 %  exactly.  Otherwise an error measure is computed by summing over all
   1621 %  pixels in an image the distance squared in RGB space between each image
   1622 %  pixel and its corresponding pixel in the reconstruct image.  The error
   1623 %  measure is assigned to these image members:
   1624 %
   1625 %    o mean_error_per_pixel:  The mean error for any single pixel in
   1626 %      the image.
   1627 %
   1628 %    o normalized_mean_error:  The normalized mean quantization error for
   1629 %      any single pixel in the image.  This distance measure is normalized to
   1630 %      a range between 0 and 1.  It is independent of the range of red, green,
   1631 %      and blue values in the image.
   1632 %
   1633 %    o normalized_maximum_error:  The normalized maximum quantization
   1634 %      error for any single pixel in the image.  This distance measure is
   1635 %      normalized to a range between 0 and 1.  It is independent of the range
   1636 %      of red, green, and blue values in your image.
   1637 %
   1638 %  A small normalized mean square error, accessed as
   1639 %  image->normalized_mean_error, suggests the images are very similar in
   1640 %  spatial layout and color.
   1641 %
   1642 %  The format of the SetImageColorMetric method is:
   1643 %
   1644 %      MagickBooleanType SetImageColorMetric(Image *image,
   1645 %        const Image *reconstruct_image,ExceptionInfo *exception)
   1646 %
   1647 %  A description of each parameter follows.
   1648 %
   1649 %    o image: the image.
   1650 %
   1651 %    o reconstruct_image: the reconstruct image.
   1652 %
   1653 %    o exception: return any errors or warnings in this structure.
   1654 %
   1655 */
   1656 MagickExport MagickBooleanType SetImageColorMetric(Image *image,
   1657   const Image *reconstruct_image,ExceptionInfo *exception)
   1658 {
   1659   CacheView
   1660     *image_view,
   1661     *reconstruct_view;
   1662 
   1663   double
   1664     area,
   1665     maximum_error,
   1666     mean_error,
   1667     mean_error_per_pixel;
   1668 
   1669   MagickBooleanType
   1670     status;
   1671 
   1672   size_t
   1673     columns,
   1674     rows;
   1675 
   1676   ssize_t
   1677     y;
   1678 
   1679   assert(image != (Image *) NULL);
   1680   assert(image->signature == MagickCoreSignature);
   1681   assert(reconstruct_image != (const Image *) NULL);
   1682   assert(reconstruct_image->signature == MagickCoreSignature);
   1683   area=0.0;
   1684   maximum_error=0.0;
   1685   mean_error_per_pixel=0.0;
   1686   mean_error=0.0;
   1687   rows=MagickMax(image->rows,reconstruct_image->rows);
   1688   columns=MagickMax(image->columns,reconstruct_image->columns);
   1689   image_view=AcquireVirtualCacheView(image,exception);
   1690   reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
   1691   for (y=0; y < (ssize_t) rows; y++)
   1692   {
   1693     register const Quantum
   1694       *magick_restrict p,
   1695       *magick_restrict q;
   1696 
   1697     register ssize_t
   1698       x;
   1699 
   1700     p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
   1701     q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
   1702     if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
   1703       break;
   1704     for (x=0; x < (ssize_t) columns; x++)
   1705     {
   1706       register ssize_t
   1707         i;
   1708 
   1709       if (GetPixelReadMask(image,p) == 0)
   1710         {
   1711           p+=GetPixelChannels(image);
   1712           q+=GetPixelChannels(reconstruct_image);
   1713           continue;
   1714         }
   1715       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
   1716       {
   1717         double
   1718           distance;
   1719 
   1720         PixelChannel channel=GetPixelChannelChannel(image,i);
   1721         PixelTrait traits=GetPixelChannelTraits(image,channel);
   1722         PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
   1723           channel);
   1724         if ((traits == UndefinedPixelTrait) ||
   1725             (reconstruct_traits == UndefinedPixelTrait) ||
   1726             ((reconstruct_traits & UpdatePixelTrait) == 0))
   1727           continue;
   1728         distance=fabs(p[i]-(double) GetPixelChannel(reconstruct_image,
   1729           channel,q));
   1730         if (distance >= MagickEpsilon)
   1731           {
   1732             mean_error_per_pixel+=distance;
   1733             mean_error+=distance*distance;
   1734             if (distance > maximum_error)
   1735               maximum_error=distance;
   1736           }
   1737         area++;
   1738       }
   1739       p+=GetPixelChannels(image);
   1740       q+=GetPixelChannels(reconstruct_image);
   1741     }
   1742   }
   1743   reconstruct_view=DestroyCacheView(reconstruct_view);
   1744   image_view=DestroyCacheView(image_view);
   1745   image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
   1746   image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
   1747     mean_error/area);
   1748   image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
   1749   status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
   1750   return(status);
   1751 }
   1752 
   1753 /*
   1755 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   1756 %                                                                             %
   1757 %                                                                             %
   1758 %                                                                             %
   1759 %   S i m i l a r i t y I m a g e                                             %
   1760 %                                                                             %
   1761 %                                                                             %
   1762 %                                                                             %
   1763 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   1764 %
   1765 %  SimilarityImage() compares the reference image of the image and returns the
   1766 %  best match offset.  In addition, it returns a similarity image such that an
   1767 %  exact match location is completely white and if none of the pixels match,
   1768 %  black, otherwise some gray level in-between.
   1769 %
   1770 %  The format of the SimilarityImageImage method is:
   1771 %
   1772 %      Image *SimilarityImage(const Image *image,const Image *reference,
   1773 %        const MetricType metric,const double similarity_threshold,
   1774 %        RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
   1775 %
   1776 %  A description of each parameter follows:
   1777 %
   1778 %    o image: the image.
   1779 %
   1780 %    o reference: find an area of the image that closely resembles this image.
   1781 %
   1782 %    o metric: the metric.
   1783 %
   1784 %    o similarity_threshold: minimum distortion for (sub)image match.
   1785 %
   1786 %    o offset: the best match offset of the reference image within the image.
   1787 %
   1788 %    o similarity: the computed similarity between the images.
   1789 %
   1790 %    o exception: return any errors or warnings in this structure.
   1791 %
   1792 */
   1793 
   1794 static double GetSimilarityMetric(const Image *image,const Image *reference,
   1795   const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
   1796   ExceptionInfo *exception)
   1797 {
   1798   double
   1799     distortion;
   1800 
   1801   Image
   1802     *similarity_image;
   1803 
   1804   MagickBooleanType
   1805     status;
   1806 
   1807   RectangleInfo
   1808     geometry;
   1809 
   1810   SetGeometry(reference,&geometry);
   1811   geometry.x=x_offset;
   1812   geometry.y=y_offset;
   1813   similarity_image=CropImage(image,&geometry,exception);
   1814   if (similarity_image == (Image *) NULL)
   1815     return(0.0);
   1816   distortion=0.0;
   1817   status=GetImageDistortion(similarity_image,reference,metric,&distortion,
   1818     exception);
   1819   similarity_image=DestroyImage(similarity_image);
   1820   if (status == MagickFalse)
   1821     return(0.0);
   1822   return(distortion);
   1823 }
   1824 
   1825 MagickExport Image *SimilarityImage(const Image *image,const Image *reference,
   1826   const MetricType metric,const double similarity_threshold,
   1827   RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
   1828 {
   1829 #define SimilarityImageTag  "Similarity/Image"
   1830 
   1831   CacheView
   1832     *similarity_view;
   1833 
   1834   Image
   1835     *similarity_image;
   1836 
   1837   MagickBooleanType
   1838     status;
   1839 
   1840   MagickOffsetType
   1841     progress;
   1842 
   1843   ssize_t
   1844     y;
   1845 
   1846   assert(image != (const Image *) NULL);
   1847   assert(image->signature == MagickCoreSignature);
   1848   if (image->debug != MagickFalse)
   1849     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
   1850   assert(exception != (ExceptionInfo *) NULL);
   1851   assert(exception->signature == MagickCoreSignature);
   1852   assert(offset != (RectangleInfo *) NULL);
   1853   SetGeometry(reference,offset);
   1854   *similarity_metric=MagickMaximumValue;
   1855   similarity_image=CloneImage(image,image->columns-reference->columns+1,
   1856     image->rows-reference->rows+1,MagickTrue,exception);
   1857   if (similarity_image == (Image *) NULL)
   1858     return((Image *) NULL);
   1859   status=SetImageStorageClass(similarity_image,DirectClass,exception);
   1860   if (status == MagickFalse)
   1861     {
   1862       similarity_image=DestroyImage(similarity_image);
   1863       return((Image *) NULL);
   1864     }
   1865   (void) SetImageAlphaChannel(similarity_image,DeactivateAlphaChannel,
   1866     exception);
   1867   /*
   1868     Measure similarity of reference image against image.
   1869   */
   1870   status=MagickTrue;
   1871   progress=0;
   1872   similarity_view=AcquireAuthenticCacheView(similarity_image,exception);
   1873 #if defined(MAGICKCORE_OPENMP_SUPPORT)
   1874   #pragma omp parallel for schedule(static,4) \
   1875     shared(progress,status,similarity_metric) \
   1876     magick_threads(image,image,image->rows,1)
   1877 #endif
   1878   for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
   1879   {
   1880     double
   1881       similarity;
   1882 
   1883     register Quantum
   1884       *magick_restrict q;
   1885 
   1886     register ssize_t
   1887       x;
   1888 
   1889     if (status == MagickFalse)
   1890       continue;
   1891 #if defined(MAGICKCORE_OPENMP_SUPPORT)
   1892     #pragma omp flush(similarity_metric)
   1893 #endif
   1894     if (*similarity_metric <= similarity_threshold)
   1895       continue;
   1896     q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
   1897       1,exception);
   1898     if (q == (Quantum *) NULL)
   1899       {
   1900         status=MagickFalse;
   1901         continue;
   1902       }
   1903     for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
   1904     {
   1905       register ssize_t
   1906         i;
   1907 
   1908 #if defined(MAGICKCORE_OPENMP_SUPPORT)
   1909       #pragma omp flush(similarity_metric)
   1910 #endif
   1911       if (*similarity_metric <= similarity_threshold)
   1912         break;
   1913       similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
   1914 #if defined(MAGICKCORE_OPENMP_SUPPORT)
   1915       #pragma omp critical (MagickCore_SimilarityImage)
   1916 #endif
   1917       if ((metric == NormalizedCrossCorrelationErrorMetric) ||
   1918           (metric == UndefinedErrorMetric))
   1919         similarity=1.0-similarity;
   1920       if (similarity < *similarity_metric)
   1921         {
   1922           offset->x=x;
   1923           offset->y=y;
   1924           *similarity_metric=similarity;
   1925         }
   1926       if (metric == PerceptualHashErrorMetric)
   1927         similarity=MagickMin(0.01*similarity,1.0);
   1928       if (GetPixelReadMask(similarity_image,q) == 0)
   1929         {
   1930           SetPixelBackgoundColor(similarity_image,q);
   1931           q+=GetPixelChannels(similarity_image);
   1932           continue;
   1933         }
   1934       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
   1935       {
   1936         PixelChannel channel=GetPixelChannelChannel(image,i);
   1937         PixelTrait traits=GetPixelChannelTraits(image,channel);
   1938         PixelTrait similarity_traits=GetPixelChannelTraits(similarity_image,
   1939           channel);
   1940         if ((traits == UndefinedPixelTrait) ||
   1941             (similarity_traits == UndefinedPixelTrait) ||
   1942             ((similarity_traits & UpdatePixelTrait) == 0))
   1943           continue;
   1944         SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
   1945           QuantumRange*similarity),q);
   1946       }
   1947       q+=GetPixelChannels(similarity_image);
   1948     }
   1949     if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
   1950       status=MagickFalse;
   1951     if (image->progress_monitor != (MagickProgressMonitor) NULL)
   1952       {
   1953         MagickBooleanType
   1954           proceed;
   1955 
   1956 #if defined(MAGICKCORE_OPENMP_SUPPORT)
   1957         #pragma omp critical (MagickCore_SimilarityImage)
   1958 #endif
   1959         proceed=SetImageProgress(image,SimilarityImageTag,progress++,
   1960           image->rows);
   1961         if (proceed == MagickFalse)
   1962           status=MagickFalse;
   1963       }
   1964   }
   1965   similarity_view=DestroyCacheView(similarity_view);
   1966   if (status == MagickFalse)
   1967     similarity_image=DestroyImage(similarity_image);
   1968   return(similarity_image);
   1969 }
   1970