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-2008 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 "magick/studio.h"
00044 #include "magick/artifact.h"
00045 #include "magick/cache-view.h"
00046 #include "magick/client.h"
00047 #include "magick/color.h"
00048 #include "magick/color-private.h"
00049 #include "magick/colorspace.h"
00050 #include "magick/colorspace-private.h"
00051 #include "magick/compare.h"
00052 #include "magick/composite-private.h"
00053 #include "magick/constitute.h"
00054 #include "magick/exception-private.h"
00055 #include "magick/geometry.h"
00056 #include "magick/image-private.h"
00057 #include "magick/list.h"
00058 #include "magick/log.h"
00059 #include "magick/memory_.h"
00060 #include "magick/option.h"
00061 #include "magick/pixel-private.h"
00062 #include "magick/resource_.h"
00063 #include "magick/string_.h"
00064 #include "magick/utility.h"
00065 #include "magick/version.h"
00066 
00067 /*
00068 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00069 %                                                                             %
00070 %                                                                             %
00071 %                                                                             %
00072 %   C o m p a r e I m a g e C h a n n e l s                                   %
00073 %                                                                             %
00074 %                                                                             %
00075 %                                                                             %
00076 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00077 %
00078 %  CompareImageChannels() compares one or more image channels of an image
00079 %  to a reconstructed image and returns the difference image.
00080 %
00081 %  The format of the CompareImageChannels method is:
00082 %
00083 %      Image *CompareImageChannels(const Image *image,
00084 %        const Image *reconstruct_image,const ChannelType channel,
00085 %        const MetricType metric,double *distortion,ExceptionInfo *exception)
00086 %
00087 %  A description of each parameter follows:
00088 %
00089 %    o image: the image.
00090 %
00091 %    o reconstruct_image: the reconstruct image.
00092 %
00093 %    o channel: the channel.
00094 %
00095 %    o metric: the metric.
00096 %
00097 %    o distortion: the computed distortion between the images.
00098 %
00099 %    o exception: return any errors or warnings in this structure.
00100 %
00101 */
00102 
00103 MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
00104   const MetricType metric,double *distortion,ExceptionInfo *exception)
00105 {
00106   Image
00107     *highlight_image;
00108 
00109   highlight_image=CompareImageChannels(image,reconstruct_image,AllChannels,
00110     metric,distortion,exception);
00111   return(highlight_image);
00112 }
00113 
00114 MagickExport Image *CompareImageChannels(Image *image,
00115   const Image *reconstruct_image,const ChannelType channel,
00116   const MetricType metric,double *distortion,ExceptionInfo *exception)
00117 {
00118   const char
00119     *artifact;
00120 
00121   Image
00122     *difference_image,
00123     *highlight_image;
00124 
00125   long
00126     y;
00127 
00128   MagickBooleanType
00129     status;
00130 
00131   MagickPixelPacket
00132     highlight,
00133     lowlight,
00134     zero;
00135 
00136   ViewInfo
00137     **highlight_view,
00138     **image_view,
00139     **reconstruct_view;
00140 
00141   assert(image != (Image *) NULL);
00142   assert(image->signature == MagickSignature);
00143   if (image->debug != MagickFalse)
00144     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
00145   assert(reconstruct_image != (const Image *) NULL);
00146   assert(reconstruct_image->signature == MagickSignature);
00147   assert(distortion != (double *) NULL);
00148   *distortion=0.0;
00149   if (image->debug != MagickFalse)
00150     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
00151   if ((reconstruct_image->columns != image->columns) ||
00152       (reconstruct_image->rows != image->rows))
00153     ThrowImageException(ImageError,"ImageSizeDiffers");
00154   status=GetImageChannelDistortion(image,reconstruct_image,channel,metric,
00155     distortion,exception);
00156   if (status == MagickFalse)
00157     return((Image *) NULL);
00158   difference_image=CloneImage(image,0,0,MagickTrue,exception);
00159   if (difference_image == (Image *) NULL)
00160     return((Image *) NULL);
00161   (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel);
00162   highlight_image=CloneImage(image,image->columns,image->rows,MagickTrue,
00163     exception);
00164   if (highlight_image == (Image *) NULL)
00165     {
00166       difference_image=DestroyImage(difference_image);
00167       return((Image *) NULL);
00168     }
00169   if (SetImageStorageClass(highlight_image,DirectClass) == MagickFalse)
00170     {
00171       InheritException(exception,&highlight_image->exception);
00172       difference_image=DestroyImage(difference_image);
00173       highlight_image=DestroyImage(highlight_image);
00174       return((Image *) NULL);
00175     }
00176   (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel);
00177   (void) QueryMagickColor("#f1001ecc",&highlight,exception);
00178   artifact=GetImageArtifact(image,"highlight-color");
00179   if (artifact != (const char *) NULL)
00180     (void) QueryMagickColor(artifact,&highlight,exception);
00181   (void) QueryMagickColor("#ffffffcc",&lowlight,exception);
00182   artifact=GetImageArtifact(image,"lowlight-color");
00183   if (artifact != (const char *) NULL)
00184     (void) QueryMagickColor(artifact,&lowlight,exception);
00185   if (highlight_image->colorspace == CMYKColorspace)
00186     {
00187       ConvertRGBToCMYK(&highlight);
00188       ConvertRGBToCMYK(&lowlight);
00189     }
00190   /*
00191     Generate difference image.
00192   */
00193   status=MagickTrue;
00194   GetMagickPixelPacket(image,&zero);
00195   image_view=AcquireCacheViewThreadSet(image);
00196   reconstruct_view=AcquireCacheViewThreadSet(reconstruct_image);
00197   highlight_view=AcquireCacheViewThreadSet(highlight_image);
00198 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00199   #pragma omp parallel for schedule(dynamic,8) shared(status)
00200 #endif
00201   for (y=0; y < (long) image->rows; y++)
00202   {
00203     MagickBooleanType
00204       sync;
00205 
00206     MagickPixelPacket
00207       pixel,
00208       reconstruct_pixel;
00209 
00210     register const IndexPacket
00211       *indexes,
00212       *reconstruct_indexes;
00213 
00214     register const PixelPacket
00215       *p,
00216       *q;
00217 
00218     register IndexPacket
00219       *highlight_indexes;
00220 
00221     register long
00222       id,
00223       x;
00224 
00225     register PixelPacket
00226       *r;
00227 
00228     if (status == MagickFalse)
00229       continue;
00230     id=GetCacheViewThreadId();
00231     p=GetCacheViewVirtualPixels(image_view[id],0,y,image->columns,1,
00232       exception);
00233     q=GetCacheViewVirtualPixels(reconstruct_view[id],0,y,
00234       reconstruct_image->columns,1,exception);
00235     r=QueueCacheViewAuthenticPixels(highlight_view[id],0,y,
00236       highlight_image->columns,1,exception);
00237     if ((p == (const PixelPacket *) NULL) ||
00238         (q == (const PixelPacket *) NULL) || (r == (PixelPacket *) NULL))
00239       {
00240         status=MagickFalse;
00241         continue;
00242       }
00243     indexes=GetCacheViewVirtualIndexes(image_view[id]);
00244     reconstruct_indexes=GetCacheViewVirtualIndexes(reconstruct_view[id]);
00245     highlight_indexes=GetCacheViewAuthenticIndexes(highlight_view[id]);
00246     pixel=zero;
00247     reconstruct_pixel=zero;
00248     for (x=0; x < (long) image->columns; x++)
00249     {
00250       MagickStatusType
00251         difference;
00252 
00253       SetMagickPixelPacket(image,p,indexes+x,&pixel);
00254       SetMagickPixelPacket(reconstruct_image,q,reconstruct_indexes+x,
00255         &reconstruct_pixel);
00256       difference=MagickFalse;
00257       if (channel == AllChannels)
00258         {
00259           if (IsMagickColorSimilar(&pixel,&reconstruct_pixel) == MagickFalse)
00260             difference=MagickTrue;
00261         }
00262       else
00263         {
00264           if (((channel & RedChannel) != 0) && (p->red != q->red))
00265             difference=MagickTrue;
00266           if (((channel & GreenChannel) != 0) && (p->green != q->green))
00267             difference=MagickTrue;
00268           if (((channel & BlueChannel) != 0) && (p->blue != q->blue))
00269             difference=MagickTrue;
00270           if (((channel & OpacityChannel) != 0) &&
00271               (image->matte != MagickFalse) && (p->opacity != q->opacity))
00272             difference=MagickTrue;
00273           if ((((channel & IndexChannel) != 0) &&
00274                (image->colorspace == CMYKColorspace) &&
00275                (reconstruct_image->colorspace == CMYKColorspace)) &&
00276               (indexes[x] != reconstruct_indexes[x]))
00277             difference=MagickTrue;
00278         }
00279       if (difference != MagickFalse)
00280         SetPixelPacket(highlight_image,&highlight,r,highlight_indexes+x);
00281       else
00282         SetPixelPacket(highlight_image,&lowlight,r,highlight_indexes+x);
00283       p++;
00284       q++;
00285       r++;
00286     }
00287     sync=SyncCacheViewAuthenticPixels(highlight_view[id],exception);
00288     if (sync == MagickFalse)
00289       status=MagickFalse;
00290   }
00291   highlight_view=DestroyCacheViewThreadSet(highlight_view);
00292   reconstruct_view=DestroyCacheViewThreadSet(reconstruct_view);
00293   image_view=DestroyCacheViewThreadSet(image_view);
00294   (void) CompositeImage(difference_image,image->compose,highlight_image,0,0);
00295   highlight_image=DestroyImage(highlight_image);
00296   if (status == MagickFalse)
00297     difference_image=DestroyImage(difference_image);
00298   return(difference_image);
00299 }
00300 
00301 /*
00302 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00303 %                                                                             %
00304 %                                                                             %
00305 %                                                                             %
00306 %   G e t I m a g e C h a n n e l D i s t o r t i o n                         %
00307 %                                                                             %
00308 %                                                                             %
00309 %                                                                             %
00310 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00311 %
00312 %  GetImageChannelDistortion() compares one or more image channels of an image
00313 %  to a reconstructed image and returns the specified distortion metric.
00314 %
00315 %  The format of the CompareImageChannels method is:
00316 %
00317 %      MagickBooleanType GetImageChannelDistortion(const Image *image,
00318 %        const Image *reconstruct_image,const ChannelType channel,
00319 %        const MetricType metric,double *distortion,ExceptionInfo *exception)
00320 %
00321 %  A description of each parameter follows:
00322 %
00323 %    o image: the image.
00324 %
00325 %    o reconstruct_image: the reconstruct image.
00326 %
00327 %    o channel: the channel.
00328 %
00329 %    o metric: the metric.
00330 %
00331 %    o distortion: the computed distortion between the images.
00332 %
00333 %    o exception: return any errors or warnings in this structure.
00334 %
00335 */
00336 
00337 MagickExport MagickBooleanType GetImageDistortion(Image *image,
00338   const Image *reconstruct_image,const MetricType metric,double *distortion,
00339   ExceptionInfo *exception)
00340 {
00341   MagickBooleanType
00342     status;
00343 
00344   status=GetImageChannelDistortion(image,reconstruct_image,AllChannels,
00345     metric,distortion,exception);
00346   return(status);
00347 }
00348 
00349 static MagickBooleanType GetAbsoluteError(const Image *image,
00350   const Image *reconstruct_image,const ChannelType channel,double *distortion,
00351   ExceptionInfo *exception)
00352 {
00353   long
00354     y;
00355 
00356   MagickBooleanType
00357     status;
00358 
00359   MagickPixelPacket
00360     zero;
00361 
00362   ViewInfo
00363     **image_view,
00364     **reconstruct_view;
00365 
00366   /*
00367     Compute the absolute difference in pixels between two images.
00368   */
00369   status=MagickTrue;
00370   GetMagickPixelPacket(image,&zero);
00371   image_view=AcquireCacheViewThreadSet(image);
00372   reconstruct_view=AcquireCacheViewThreadSet(reconstruct_image);
00373 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00374   #pragma omp parallel for schedule(dynamic,8) shared(status)
00375 #endif
00376   for (y=0; y < (long) image->rows; y++)
00377   {
00378     double
00379       channel_distortion[AllChannels+1];
00380 
00381     MagickPixelPacket
00382       pixel,
00383       reconstruct_pixel;
00384 
00385     register const IndexPacket
00386       *indexes,
00387       *reconstruct_indexes;
00388 
00389     register const PixelPacket
00390       *p,
00391       *q;
00392 
00393     register long
00394       i,
00395       id,
00396       x;
00397 
00398     if (status == MagickFalse)
00399       continue;
00400     id=GetCacheViewThreadId();
00401     p=GetCacheViewVirtualPixels(image_view[id],0,y,image->columns,1,exception);
00402     q=GetCacheViewVirtualPixels(reconstruct_view[id],0,y,
00403       reconstruct_image->columns,1,exception);
00404     if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
00405       {
00406         status=MagickFalse;
00407         continue;
00408       }
00409     indexes=GetCacheViewVirtualIndexes(image_view[id]);
00410     reconstruct_indexes=GetCacheViewVirtualIndexes(reconstruct_view[id]);
00411     pixel=zero;
00412     reconstruct_pixel=pixel;
00413     (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
00414     for (x=0; x < (long) image->columns; x++)
00415     {
00416       SetMagickPixelPacket(image,p,indexes+x,&pixel);
00417       SetMagickPixelPacket(reconstruct_image,q,reconstruct_indexes+x,
00418         &reconstruct_pixel);
00419       if (IsMagickColorSimilar(&pixel,&reconstruct_pixel) == MagickFalse)
00420         {
00421           if ((channel & RedChannel) != 0)
00422             channel_distortion[RedChannel]++;
00423           if ((channel & GreenChannel) != 0)
00424             channel_distortion[GreenChannel]++;
00425           if ((channel & BlueChannel) != 0)
00426             channel_distortion[BlueChannel]++;
00427           if (((channel & OpacityChannel) != 0) &&
00428               (image->matte != MagickFalse))
00429             channel_distortion[OpacityChannel]++;
00430           if (((channel & IndexChannel) != 0) &&
00431               (image->colorspace == CMYKColorspace))
00432             channel_distortion[BlackChannel]++;
00433           channel_distortion[AllChannels]++;
00434         }
00435       p++;
00436       q++;
00437     }
00438 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00439   #pragma omp critical
00440 #endif
00441     for (i=0; i <= (long) AllChannels; i++)
00442       distortion[i]+=channel_distortion[i];
00443   }
00444   reconstruct_view=DestroyCacheViewThreadSet(reconstruct_view);
00445   image_view=DestroyCacheViewThreadSet(image_view);
00446   return(status);
00447 }
00448 
00449 static unsigned long GetNumberChannels(const Image *image,
00450   const ChannelType channel)
00451 {
00452   unsigned long
00453     channels;
00454 
00455   channels=0;
00456   if ((channel & RedChannel) != 0)
00457     channels++;
00458   if ((channel & GreenChannel) != 0)
00459     channels++;
00460   if ((channel & BlueChannel) != 0)
00461     channels++;
00462   if (((channel & OpacityChannel) != 0) &&
00463        (image->matte != MagickFalse))
00464     channels++;
00465   if (((channel & IndexChannel) != 0) &&
00466       (image->colorspace == CMYKColorspace))
00467     channels++;
00468   return(channels);
00469 }
00470 
00471 static MagickBooleanType GetMeanAbsoluteError(const Image *image,
00472   const Image *reconstruct_image,const ChannelType channel,
00473   double *distortion,ExceptionInfo *exception)
00474 {
00475   double
00476     scale;
00477 
00478   long
00479     y;
00480 
00481   MagickBooleanType
00482     status;
00483 
00484   ViewInfo
00485     **image_view,
00486     **reconstruct_view;
00487 
00488   status=MagickTrue;
00489   scale=1.0/((double) image->columns*image->rows);
00490   image_view=AcquireCacheViewThreadSet(image);
00491   reconstruct_view=AcquireCacheViewThreadSet(reconstruct_image);
00492 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00493   #pragma omp parallel for schedule(dynamic,8) shared(status)
00494 #endif
00495   for (y=0; y < (long) image->rows; y++)
00496   {
00497     double
00498       channel_distortion[AllChannels+1];
00499 
00500     register const IndexPacket
00501       *indexes,
00502       *reconstruct_indexes;
00503 
00504     register const PixelPacket
00505       *p,
00506       *q;
00507 
00508     register long
00509       i,
00510       id,
00511       x;
00512 
00513     if (status == MagickFalse)
00514       continue;
00515     id=GetCacheViewThreadId();
00516     p=GetCacheViewVirtualPixels(image_view[id],0,y,image->columns,1,exception);
00517     q=GetCacheViewVirtualPixels(reconstruct_view[id],0,y,
00518       reconstruct_image->columns,1,exception);
00519     if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
00520       {
00521         status=MagickFalse;
00522         continue;
00523       }
00524     indexes=GetCacheViewVirtualIndexes(image_view[id]);
00525     reconstruct_indexes=GetCacheViewVirtualIndexes(reconstruct_view[id]);
00526     (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
00527     for (x=0; x < (long) image->columns; x++)
00528     {
00529       MagickRealType
00530         distance;
00531 
00532       if ((channel & RedChannel) != 0)
00533         {
00534           distance=fabs(p->red-(double) q->red);
00535           channel_distortion[RedChannel]+=scale*distance;
00536           channel_distortion[AllChannels]+=scale*distance;
00537         }
00538       if ((channel & GreenChannel) != 0)
00539         {
00540           distance=fabs(p->green-(double) q->green);
00541           channel_distortion[GreenChannel]+=scale*distance;
00542           channel_distortion[AllChannels]+=scale*distance;
00543         }
00544       if ((channel & BlueChannel) != 0)
00545         {
00546           distance=fabs(p->blue-(double) q->blue);
00547           channel_distortion[BlueChannel]+=scale*distance;
00548           channel_distortion[AllChannels]+=scale*distance;
00549         }
00550       if (((channel & OpacityChannel) != 0) &&
00551           (image->matte != MagickFalse))
00552         {
00553           distance=fabs(p->opacity-(double) q->opacity);
00554           channel_distortion[OpacityChannel]+=scale*distance;
00555           channel_distortion[AllChannels]+=scale*distance;
00556         }
00557       if (((channel & IndexChannel) != 0) &&
00558           (image->colorspace == CMYKColorspace))
00559         {
00560           distance=fabs(indexes[x]-(double) reconstruct_indexes[x]);
00561           channel_distortion[BlackChannel]+=scale*distance;
00562           channel_distortion[AllChannels]+=scale*distance;
00563         }
00564       p++;
00565       q++;
00566     }
00567 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00568   #pragma omp critical
00569 #endif
00570     for (i=0; i <= (long) AllChannels; i++)
00571       distortion[i]+=channel_distortion[i];
00572   }
00573   reconstruct_view=DestroyCacheViewThreadSet(reconstruct_view);
00574   image_view=DestroyCacheViewThreadSet(image_view);
00575   distortion[AllChannels]/=(double) GetNumberChannels(image,channel);
00576   return(status);
00577 }
00578 
00579 static MagickBooleanType GetMeanErrorPerPixel(Image *image,
00580   const Image *reconstruct_image,const ChannelType channel,double *distortion,
00581   ExceptionInfo *exception)
00582 {
00583   long
00584     y;
00585 
00586   MagickBooleanType
00587     status;
00588 
00589   MagickRealType
00590     alpha,
00591     area,
00592     beta,
00593     maximum_error,
00594     mean_error;
00595 
00596   ViewInfo
00597     *image_view,
00598     *reconstruct_view;
00599 
00600   status=MagickTrue;
00601   alpha=1.0;
00602   beta=1.0;
00603   area=0.0;
00604   maximum_error=0.0;
00605   mean_error=0.0;
00606   image_view=AcquireCacheView(image);
00607   reconstruct_view=AcquireCacheView(reconstruct_image);
00608   for (y=0; y < (long) image->rows; y++)
00609   {
00610     register const IndexPacket
00611       *indexes,
00612       *reconstruct_indexes;
00613 
00614     register const PixelPacket
00615       *p,
00616       *q;
00617 
00618     register long
00619       x;
00620 
00621     p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
00622     q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,1,
00623       exception);
00624     if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
00625       {
00626         status=MagickFalse;
00627         break;
00628       }
00629     indexes=GetCacheViewVirtualIndexes(image_view);
00630     reconstruct_indexes=GetCacheViewVirtualIndexes(reconstruct_view);
00631     for (x=0; x < (long) image->columns; x++)
00632     {
00633       MagickRealType
00634         distance;
00635 
00636       if ((channel & OpacityChannel) != 0)
00637         {
00638           if (image->matte != MagickFalse)
00639             alpha=(MagickRealType) (QuantumScale*(QuantumRange-p->opacity));
00640           if (reconstruct_image->matte != MagickFalse)
00641             beta=(MagickRealType) (QuantumScale*(QuantumRange-q->opacity));
00642         }
00643       if ((channel & RedChannel) != 0)
00644         {
00645           distance=fabs(alpha*p->red-beta*q->red);
00646           distortion[RedChannel]+=distance;
00647           distortion[AllChannels]+=distance;
00648           mean_error+=distance*distance;
00649           if (distance > maximum_error)
00650             maximum_error=distance;
00651           area++;
00652         }
00653       if ((channel & GreenChannel) != 0)
00654         {
00655           distance=fabs(alpha*p->green-beta*q->green);
00656           distortion[GreenChannel]+=distance;
00657           distortion[AllChannels]+=distance;
00658           mean_error+=distance*distance;
00659           if (distance > maximum_error)
00660             maximum_error=distance;
00661           area++;
00662         }
00663       if ((channel & BlueChannel) != 0)
00664         {
00665           distance=fabs(alpha*p->blue-beta*q->blue);
00666           distortion[BlueChannel]+=distance;
00667           distortion[AllChannels]+=distance;
00668           mean_error+=distance*distance;
00669           if (distance > maximum_error)
00670             maximum_error=distance;
00671           area++;
00672         }
00673       if (((channel & OpacityChannel) != 0) &&
00674           (image->matte != MagickFalse))
00675         {
00676           distance=fabs((double) p->opacity-q->opacity);
00677           distortion[OpacityChannel]+=distance;
00678           distortion[AllChannels]+=distance;
00679           mean_error+=distance*distance;
00680           if (distance > maximum_error)
00681             maximum_error=distance;
00682           area++;
00683         }
00684       if (((channel & IndexChannel) != 0) &&
00685           (image->colorspace == CMYKColorspace) &&
00686           (reconstruct_image->colorspace == CMYKColorspace))
00687         {
00688           distance=fabs(alpha*indexes[x]-beta*reconstruct_indexes[x]);
00689           distortion[BlackChannel]+=distance;
00690           distortion[AllChannels]+=distance;
00691           mean_error+=distance*distance;
00692           if (distance > maximum_error)
00693             maximum_error=distance;
00694           area++;
00695         }
00696       p++;
00697       q++;
00698     }
00699   }
00700   reconstruct_view=DestroyCacheView(reconstruct_view);
00701   image_view=DestroyCacheView(image_view);
00702   image->error.mean_error_per_pixel=distortion[AllChannels]/area;
00703   image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
00704   image->error.normalized_maximum_error=QuantumScale*maximum_error;
00705   return(status);
00706 }
00707 
00708 static MagickBooleanType GetMeanSquaredError(const Image *image,
00709   const Image *reconstruct_image,const ChannelType channel,
00710   double *distortion,ExceptionInfo *exception)
00711 {
00712   double
00713     scale;
00714 
00715   long
00716     y;
00717 
00718   MagickBooleanType
00719     status;
00720 
00721   ViewInfo
00722     **image_view,
00723     **reconstruct_view;
00724 
00725   status=MagickTrue;
00726   scale=QuantumScale/((double) image->columns*image->rows);
00727   image_view=AcquireCacheViewThreadSet(image);
00728   reconstruct_view=AcquireCacheViewThreadSet(reconstruct_image);
00729 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00730   #pragma omp parallel for schedule(dynamic,8) shared(status)
00731 #endif
00732   for (y=0; y < (long) image->rows; y++)
00733   {
00734     double
00735       channel_distortion[AllChannels+1];
00736 
00737     register const IndexPacket
00738       *indexes,
00739       *reconstruct_indexes;
00740 
00741     register const PixelPacket
00742       *p,
00743       *q;
00744 
00745     register long
00746       i,
00747       id,
00748       x;
00749 
00750     if (status == MagickFalse)
00751       continue;
00752     id=GetCacheViewThreadId();
00753     p=GetCacheViewVirtualPixels(image_view[id],0,y,image->columns,1,exception);
00754     q=GetCacheViewVirtualPixels(reconstruct_view[id],0,y,
00755       reconstruct_image->columns,1,exception);
00756     if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
00757       {
00758         status=MagickFalse;
00759         continue;
00760       }
00761     indexes=GetCacheViewVirtualIndexes(image_view[id]);
00762     reconstruct_indexes=GetCacheViewVirtualIndexes(reconstruct_view[id]);
00763     (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
00764     for (x=0; x < (long) image->columns; x++)
00765     {
00766       MagickRealType
00767         distance;
00768 
00769       if ((channel & RedChannel) != 0)
00770         {
00771           distance=(p->red-(MagickRealType) q->red);
00772           channel_distortion[RedChannel]+=scale*distance*distance;
00773           channel_distortion[AllChannels]+=scale*distance*distance;
00774         }
00775       if ((channel & GreenChannel) != 0)
00776         {
00777           distance=(p->green-(MagickRealType) q->green);
00778           channel_distortion[GreenChannel]+=scale*distance*distance;
00779           channel_distortion[AllChannels]+=scale*distance*distance;
00780         }
00781       if ((channel & BlueChannel) != 0)
00782         {
00783           distance=(p->blue-(MagickRealType) q->blue);
00784           channel_distortion[BlueChannel]+=scale*distance*distance;
00785           channel_distortion[AllChannels]+=scale*distance*distance;
00786         }
00787       if (((channel & OpacityChannel) != 0) &&
00788           (image->matte != MagickFalse))
00789         {
00790           distance=(p->opacity-(MagickRealType) q->opacity);
00791           channel_distortion[OpacityChannel]+=scale*distance*distance;
00792           channel_distortion[AllChannels]+=scale*distance*distance;
00793         }
00794       if (((channel & IndexChannel) != 0) &&
00795           (image->colorspace == CMYKColorspace) &&
00796           (reconstruct_image->colorspace == CMYKColorspace))
00797         {
00798           distance=(indexes[x]-(MagickRealType) reconstruct_indexes[x]);
00799           channel_distortion[BlackChannel]+=scale*distance*distance;
00800           channel_distortion[AllChannels]+=scale*distance*distance;
00801         }
00802       p++;
00803       q++;
00804     }
00805 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00806   #pragma omp critical
00807 #endif
00808     for (i=0; i <= (long) AllChannels; i++)
00809       distortion[i]+=channel_distortion[i];
00810   }
00811   reconstruct_view=DestroyCacheViewThreadSet(reconstruct_view);
00812   image_view=DestroyCacheViewThreadSet(image_view);
00813   distortion[AllChannels]/=(double) GetNumberChannels(image,channel);
00814   return(status);
00815 }
00816 
00817 static MagickBooleanType GetPeakAbsoluteError(const Image *image,
00818   const Image *reconstruct_image,const ChannelType channel,
00819   double *distortion,ExceptionInfo *exception)
00820 {
00821   long
00822     y;
00823 
00824   MagickBooleanType
00825     status;
00826 
00827   ViewInfo
00828     **image_view,
00829     **reconstruct_view;
00830 
00831   status=MagickTrue;
00832   image_view=AcquireCacheViewThreadSet(image);
00833   reconstruct_view=AcquireCacheViewThreadSet(reconstruct_image);
00834 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00835   #pragma omp parallel for schedule(dynamic,8) shared(status)
00836 #endif
00837   for (y=0; y < (long) image->rows; y++)
00838   {
00839     double
00840       channel_distortion[AllChannels+1];
00841 
00842     register const IndexPacket
00843       *indexes,
00844       *reconstruct_indexes;
00845 
00846     register const PixelPacket
00847       *p,
00848       *q;
00849 
00850     register long
00851       i,
00852       id,
00853       x;
00854 
00855     if (status == MagickFalse)
00856       continue;
00857     id=GetCacheViewThreadId();
00858     p=GetCacheViewVirtualPixels(image_view[id],0,y,image->columns,1,exception);
00859     q=GetCacheViewVirtualPixels(reconstruct_view[id],0,y,
00860       reconstruct_image->columns,1,exception);
00861     if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
00862       {
00863         status=MagickFalse;
00864         continue;
00865       }
00866     indexes=GetCacheViewVirtualIndexes(image_view[id]);
00867     reconstruct_indexes=GetCacheViewVirtualIndexes(reconstruct_view[id]);
00868     (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
00869     for (x=0; x < (long) image->columns; x++)
00870     {
00871       MagickRealType
00872         distance;
00873 
00874       if ((channel & RedChannel) != 0)
00875         {
00876           distance=fabs(p->red-(double) q->red);
00877           if (distance > channel_distortion[RedChannel])
00878             channel_distortion[RedChannel]=distance;
00879           if (distance > channel_distortion[AllChannels])
00880             channel_distortion[AllChannels]=distance;
00881         }
00882       if ((channel & GreenChannel) != 0)
00883         {
00884           distance=fabs(p->green-(double) q->green);
00885           if (distance > channel_distortion[GreenChannel])
00886             channel_distortion[GreenChannel]=distance;
00887           if (distance > channel_distortion[AllChannels])
00888             channel_distortion[AllChannels]=distance;
00889         }
00890       if ((channel & BlueChannel) != 0)
00891         {
00892           distance=fabs(p->blue-(double) q->blue);
00893           if (distance > channel_distortion[BlueChannel])
00894             channel_distortion[BlueChannel]=distance;
00895           if (distance > channel_distortion[AllChannels])
00896             channel_distortion[AllChannels]=distance;
00897         }
00898       if (((channel & OpacityChannel) != 0) &&
00899           (image->matte != MagickFalse))
00900         {
00901           distance=fabs(p->opacity-(double) q->opacity);
00902           if (distance > channel_distortion[OpacityChannel])
00903             channel_distortion[OpacityChannel]=distance;
00904           if (distance > channel_distortion[AllChannels])
00905             channel_distortion[AllChannels]=distance;
00906         }
00907       if (((channel & IndexChannel) != 0) &&
00908           (image->colorspace == CMYKColorspace) &&
00909           (reconstruct_image->colorspace == CMYKColorspace))
00910         {
00911           distance=fabs(indexes[x]-(double) reconstruct_indexes[x]);
00912           if (distance > channel_distortion[BlackChannel])
00913             channel_distortion[BlackChannel]=distance;
00914           if (distance > channel_distortion[AllChannels])
00915             channel_distortion[AllChannels]=distance;
00916         }
00917       p++;
00918       q++;
00919     }
00920 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00921   #pragma omp critical
00922 #endif
00923     for (i=0; i <= (long) AllChannels; i++)
00924       if (channel_distortion[i] > distortion[i])
00925         distortion[i]=channel_distortion[i];
00926   }
00927   reconstruct_view=DestroyCacheViewThreadSet(reconstruct_view);
00928   image_view=DestroyCacheViewThreadSet(image_view);
00929   return(status);
00930 }
00931 
00932 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
00933   const Image *reconstruct_image,const ChannelType channel,
00934   double *distortion,ExceptionInfo *exception)
00935 {
00936   MagickBooleanType
00937     status;
00938 
00939   status=GetMeanSquaredError(image,reconstruct_image,channel,distortion,
00940     exception);
00941   if ((channel & RedChannel) != 0)
00942     distortion[RedChannel]=20.0*log10((double) 1.0/sqrt(QuantumScale*
00943       distortion[RedChannel]));
00944   if ((channel & GreenChannel) != 0)
00945     distortion[GreenChannel]=20.0*log10((double) 1.0/sqrt(QuantumScale*
00946       distortion[GreenChannel]));
00947   if ((channel & BlueChannel) != 0)
00948     distortion[BlueChannel]=20.0*log10((double) 1.0/sqrt(QuantumScale*
00949       distortion[BlueChannel]));
00950   if (((channel & OpacityChannel) != 0) &&
00951       (image->matte != MagickFalse))
00952     distortion[OpacityChannel]=20.0*log10((double) 1.0/sqrt(QuantumScale*
00953       distortion[OpacityChannel]));
00954   if (((channel & IndexChannel) != 0) &&
00955       (image->colorspace == CMYKColorspace))
00956     distortion[BlackChannel]=20.0*log10((double) 1.0/sqrt(QuantumScale*
00957       distortion[BlackChannel]));
00958   distortion[AllChannels]=20.0*log10((double) 1.0/sqrt(QuantumScale*
00959     distortion[AllChannels]));
00960   return(status);
00961 }
00962 
00963 static MagickBooleanType GetRootMeanSquaredError(const Image *image,
00964   const Image *reconstruct_image,const ChannelType channel,
00965   double *distortion,ExceptionInfo *exception)
00966 {
00967   MagickBooleanType
00968     status;
00969 
00970   status=GetMeanSquaredError(image,reconstruct_image,channel,distortion,
00971     exception);
00972   if ((channel & RedChannel) != 0)
00973     distortion[RedChannel]=sqrt(QuantumRange*distortion[RedChannel]);
00974   if ((channel & GreenChannel) != 0)
00975     distortion[GreenChannel]=sqrt(QuantumRange*distortion[GreenChannel]);
00976   if ((channel & BlueChannel) != 0)
00977     distortion[BlueChannel]=sqrt(QuantumRange*distortion[BlueChannel]);
00978   if (((channel & OpacityChannel) != 0) &&
00979       (image->matte != MagickFalse))
00980     distortion[OpacityChannel]=sqrt(QuantumRange*distortion[OpacityChannel]);
00981   if (((channel & IndexChannel) != 0) &&
00982       (image->colorspace == CMYKColorspace))
00983     distortion[BlackChannel]=sqrt(QuantumRange*distortion[BlackChannel]);
00984   distortion[AllChannels]=sqrt(QuantumRange*distortion[AllChannels]);
00985   return(status);
00986 }
00987 
00988 MagickExport MagickBooleanType GetImageChannelDistortion(Image *image,
00989   const Image *reconstruct_image,const ChannelType channel,
00990   const MetricType metric,double *distortion,ExceptionInfo *exception)
00991 {
00992   double
00993     *channel_distortion;
00994 
00995   MagickBooleanType
00996     status;
00997 
00998   size_t
00999     length;
01000 
01001   assert(image != (Image *) NULL);
01002   assert(image->signature == MagickSignature);
01003   if (image->debug != MagickFalse)
01004     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
01005   assert(reconstruct_image != (const Image *) NULL);
01006   assert(reconstruct_image->signature == MagickSignature);
01007   assert(distortion != (double *) NULL);
01008   *distortion=0.0;
01009   if (image->debug != MagickFalse)
01010     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
01011   if ((reconstruct_image->columns != image->columns) ||
01012       (reconstruct_image->rows != image->rows))
01013     ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
01014   /*
01015     Get image distortion.
01016   */
01017   length=AllChannels+1UL;
01018   channel_distortion=(double *) AcquireQuantumMemory(length,
01019     sizeof(*channel_distortion));
01020   if (channel_distortion == (double *) NULL)
01021     ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
01022   (void) ResetMagickMemory(channel_distortion,0,length*
01023     sizeof(*channel_distortion));
01024   switch (metric)
01025   {
01026     case AbsoluteErrorMetric:
01027     {