root / ImageMagick / trunk / magick / compare.c

Revision 11882, 40.9 kB (checked in by cristy, 2 weeks ago)
Line 
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%                         Image Comparison Methods                            %
14%                                                                             %
15%                              Software Design                                %
16%                                John Cristy                                  %
17%                               December 2003                                 %
18%                                                                             %
19%                                                                             %
20%  Copyright 1999-2008 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/*
41  Include declarations.
42*/
43#include "magick/studio.h"
44#include "magick/artifact.h"
45#include "magick/cache-view.h"
46#include "magick/client.h"
47#include "magick/color.h"
48#include "magick/color-private.h"
49#include "magick/colorspace.h"
50#include "magick/colorspace-private.h"
51#include "magick/compare.h"
52#include "magick/composite-private.h"
53#include "magick/constitute.h"
54#include "magick/exception-private.h"
55#include "magick/geometry.h"
56#include "magick/image-private.h"
57#include "magick/list.h"
58#include "magick/log.h"
59#include "magick/memory_.h"
60#include "magick/option.h"
61#include "magick/pixel-private.h"
62#include "magick/resource_.h"
63#include "magick/string_.h"
64#include "magick/utility.h"
65#include "magick/version.h"
66
67/*
68%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
69%                                                                             %
70%                                                                             %
71%                                                                             %
72%   C o m p a r e I m a g e C h a n n e l s                                   %
73%                                                                             %
74%                                                                             %
75%                                                                             %
76%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
77%
78%  CompareImageChannels() compares one or more image channels of an image
79%  to a reconstructed image and returns the difference image.
80%
81%  The format of the CompareImageChannels method is:
82%
83%      Image *CompareImageChannels(const Image *image,
84%        const Image *reconstruct_image,const ChannelType channel,
85%        const MetricType metric,double *distortion,ExceptionInfo *exception)
86%
87%  A description of each parameter follows:
88%
89%    o image: the image.
90%
91%    o reconstruct_image: the reconstruct image.
92%
93%    o channel: the channel.
94%
95%    o metric: the metric.
96%
97%    o distortion: the computed distortion between the images.
98%
99%    o exception: Return any errors or warnings in this structure.
100%
101*/
102
103MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
104  const MetricType metric,double *distortion,ExceptionInfo *exception)
105{
106  Image
107    *highlight_image;
108
109  highlight_image=CompareImageChannels(image,reconstruct_image,AllChannels,
110    metric,distortion,exception);
111  return(highlight_image);
112}
113
114MagickExport Image *CompareImageChannels(Image *image,
115  const Image *reconstruct_image,const ChannelType channel,
116  const MetricType metric,double *distortion,ExceptionInfo *exception)
117{
118  const char
119    *artifact;
120
121  Image
122    *difference_image,
123    *highlight_image;
124
125  long
126    y;
127
128  MagickBooleanType
129    status;
130
131  MagickPixelPacket
132    highlight,
133    lowlight;
134
135  ViewInfo
136    **highlight_view,
137    **image_view,
138    **reconstruct_view;
139
140  assert(image != (Image *) NULL);
141  assert(image->signature == MagickSignature);
142  if (image->debug != MagickFalse)
143    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
144  assert(reconstruct_image != (const Image *) NULL);
145  assert(reconstruct_image->signature == MagickSignature);
146  assert(distortion != (double *) NULL);
147  *distortion=0.0;
148  if (image->debug != MagickFalse)
149    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
150  if ((reconstruct_image->columns != image->columns) ||
151      (reconstruct_image->rows != image->rows))
152    ThrowImageException(ImageError,"ImageSizeDiffers");
153  status=GetImageChannelDistortion(image,reconstruct_image,channel,metric,
154    distortion,exception);
155  if (status == MagickFalse)
156    return((Image *) NULL);
157  difference_image=CloneImage(image,0,0,MagickTrue,exception);
158  if (difference_image == (Image *) NULL)
159    return((Image *) NULL);
160  (void) SetImageAlphaChannel(difference_image,ResetAlphaChannel);
161  highlight_image=CloneImage(image,image->columns,image->rows,MagickTrue,
162    exception);
163  if (highlight_image == (Image *) NULL)
164    {
165      difference_image=DestroyImage(difference_image);
166      return((Image *) NULL);
167    }
168  if (SetImageStorageClass(highlight_image,DirectClass) == MagickFalse)
169    {
170      InheritException(exception,&highlight_image->exception);
171      difference_image=DestroyImage(difference_image);
172      highlight_image=DestroyImage(highlight_image);
173      return((Image *) NULL);
174    }
175  (void) SetImageAlphaChannel(highlight_image,ResetAlphaChannel);
176  (void) QueryMagickColor("#f1001ecc",&highlight,exception);
177  artifact=GetImageArtifact(image,"highlight-color");
178  if (artifact != (const char *) NULL)
179    (void) QueryMagickColor(artifact,&highlight,exception);
180  (void) QueryMagickColor("#ffffffcc",&lowlight,exception);
181  artifact=GetImageArtifact(image,"lowlight-color");
182  if (artifact != (const char *) NULL)
183    (void) QueryMagickColor(artifact,&lowlight,exception);
184  if (highlight_image->colorspace == CMYKColorspace)
185    {
186      ConvertRGBToCMYK(&highlight);
187      ConvertRGBToCMYK(&lowlight);
188    }
189  /*
190    Generate difference image.
191  */
192  status=MagickTrue;
193  image_view=AcquireCacheViewThreadSet(image);
194  reconstruct_view=AcquireCacheViewThreadSet(reconstruct_image);
195  highlight_view=AcquireCacheViewThreadSet(highlight_image);
196  #pragma omp parallel for
197  for (y=0; y < (long) image->rows; y++)
198  {
199    MagickPixelPacket
200      pixel,
201      reconstruct_pixel;
202
203    register const IndexPacket
204      *indexes,
205      *reconstruct_indexes;
206
207    register const PixelPacket
208      *p,
209      *q;
210
211    register IndexPacket
212      *highlight_indexes;
213
214    register long
215      id,
216      x;
217
218    register PixelPacket
219      *r;
220
221    if (status == MagickFalse)
222      continue;
223    id=GetCacheViewThreadId();
224    p=AcquireCacheViewPixels(image_view[id],0,y,image->columns,1,exception);
225    q=AcquireCacheViewPixels(reconstruct_view[id],0,y,
226      reconstruct_image->columns,1,exception);
227    r=SetCacheViewPixels(highlight_view[id],0,y,highlight_image->columns,1);
228    if ((p == (const PixelPacket *) NULL) ||
229        (q == (const PixelPacket *) NULL) || (r == (PixelPacket *) NULL))
230      {
231        status=MagickFalse;
232        continue;
233      }
234    indexes=AcquireCacheViewIndexes(image_view[id]);
235    reconstruct_indexes=AcquireCacheViewIndexes(reconstruct_view[id]);
236    highlight_indexes=GetCacheViewIndexes(highlight_view[id]);
237    GetMagickPixelPacket(image,&pixel);
238    GetMagickPixelPacket(reconstruct_image,&reconstruct_pixel);
239    for (x=0; x < (long) image->columns; x++)
240    {
241      MagickStatusType
242        difference;
243
244      SetMagickPixelPacket(image,p,indexes+x,&pixel);
245      SetMagickPixelPacket(reconstruct_image,q,reconstruct_indexes+x,
246        &reconstruct_pixel);
247      difference=MagickFalse;
248      if (channel == AllChannels)
249        {
250          if (IsMagickColorSimilar(&pixel,&reconstruct_pixel) == MagickFalse)
251            difference=MagickTrue;
252        }
253      else
254        {
255          if (((channel & RedChannel) != 0) && (p->red != q->red))
256            difference=MagickTrue;
257          if (((channel & GreenChannel) != 0) && (p->green != q->green))
258            difference=MagickTrue;
259          if (((channel & BlueChannel) != 0) && (p->blue != q->blue))
260            difference=MagickTrue;
261          if (((channel & OpacityChannel) != 0) && (p->opacity != q->opacity))
262            difference=MagickTrue;
263          if ((((channel & IndexChannel) != 0) &&
264               (image->colorspace == CMYKColorspace) &&
265               (reconstruct_image->colorspace == CMYKColorspace)) &&
266              (indexes[x] != reconstruct_indexes[x]))
267            difference=MagickTrue;
268        }
269      if (difference != MagickFalse)
270        SetPixelPacket(highlight_image,&highlight,r,highlight_indexes+x);
271      else
272        SetPixelPacket(highlight_image,&lowlight,r,highlight_indexes+x);
273      p++;
274      q++;
275      r++;
276    }
277    if (SyncCacheView(highlight_view[id]) == MagickFalse)
278      status=MagickFalse;
279  }
280  highlight_view=DestroyCacheViewThreadSet(highlight_view);
281  reconstruct_view=DestroyCacheViewThreadSet(reconstruct_view);
282  image_view=DestroyCacheViewThreadSet(image_view);
283  (void) CompositeImage(difference_image,image->compose,highlight_image,0,0);
284  highlight_image=DestroyImage(highlight_image);
285  if (status == MagickFalse)
286    difference_image=DestroyImage(difference_image);
287  return(difference_image);
288}
289
290/*
291%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
292%                                                                             %
293%                                                                             %
294%                                                                             %
295%   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                         %
296%                                                                             %
297%                                                                             %
298%                                                                             %
299%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
300%
301%  GetImageChannelDistortion() compares one or more image channels of an image
302%  to a reconstructed image and returns the specified distortion metric.
303%
304%  The format of the CompareImageChannels method is:
305%
306%      MagickBooleanType GetImageChannelDistortion(const Image *image,
307%        const Image *reconstruct_image,const ChannelType channel,
308%        const MetricType metric,double *distortion,ExceptionInfo *exception)
309%
310%  A description of each parameter follows:
311%
312%    o image: the image.
313%
314%    o reconstruct_image: the reconstruct image.
315%
316%    o channel: the channel.
317%
318%    o metric: the metric.
319%
320%    o distortion: the computed distortion between the images.
321%
322%    o exception: Return any errors or warnings in this structure.
323%
324*/
325
326MagickExport MagickBooleanType GetImageDistortion(Image *image,
327  const Image *reconstruct_image,const MetricType metric,double *distortion,
328  ExceptionInfo *exception)
329{
330  MagickBooleanType
331    status;
332
333  status=GetImageChannelDistortion(image,reconstruct_image,AllChannels,
334    metric,distortion,exception);
335  return(status);
336}
337
338static MagickBooleanType GetAbsoluteError(const Image *image,
339  const Image *reconstruct_image,const ChannelType channel,double *distortion,
340  ExceptionInfo *exception)
341{
342  long
343    y;
344
345  MagickBooleanType
346    status;
347
348  ViewInfo
349    **image_view,
350    **reconstruct_view;
351
352  /*
353    Compute the absolute difference in pixels between two images.
354  */
355  status=MagickTrue;
356  image_view=AcquireCacheViewThreadSet(image);
357  reconstruct_view=AcquireCacheViewThreadSet(reconstruct_image);
358  #pragma omp parallel
359  for (y=0; y < (long) image->rows; y++)
360  {
361    MagickPixelPacket
362      pixel,
363      reconstruct_pixel;
364
365    register const IndexPacket
366      *indexes,
367      *reconstruct_indexes;
368
369    register const PixelPacket
370      *p,
371      *q;
372
373    register long
374      id,
375      x;
376
377    if (status == MagickFalse)
378      continue;
379    id=GetCacheViewThreadId();
380    p=AcquireCacheViewPixels(image_view[id],0,y,image->columns,1,exception);
381    q=AcquireCacheViewPixels(reconstruct_view[id],0,y,
382      reconstruct_image->columns,1,exception);
383    if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
384      {
385        status=MagickFalse;
386        continue;
387      }
388    indexes=AcquireCacheViewIndexes(image_view[id]);
389    reconstruct_indexes=AcquireCacheViewIndexes(reconstruct_view[id]);
390    GetMagickPixelPacket(image,&pixel);
391    GetMagickPixelPacket(reconstruct_image,&reconstruct_pixel);
392    for (x=0; x < (long) image->columns; x++)
393    {
394      SetMagickPixelPacket(image,p,indexes+x,&pixel);
395      SetMagickPixelPacket(reconstruct_image,q,reconstruct_indexes+x,
396        &reconstruct_pixel);
397      if (IsMagickColorSimilar(&pixel,&reconstruct_pixel) == MagickFalse)
398        {
399          if ((channel & RedChannel) != 0)
400            distortion[RedChannel]++;
401          if ((channel & GreenChannel) != 0)
402            distortion[GreenChannel]++;
403          if ((channel & BlueChannel) != 0)
404            distortion[BlueChannel]++;
405          if ((channel & OpacityChannel) != 0)
406            distortion[OpacityChannel]++;
407          if (((channel & IndexChannel) != 0) &&
408              (image->colorspace == CMYKColorspace))
409            distortion[BlackChannel]++;
410          distortion[AllChannels]++;
411        }
412      p++;
413      q++;
414    }
415  }
416  reconstruct_view=DestroyCacheViewThreadSet(reconstruct_view);
417  image_view=DestroyCacheViewThreadSet(image_view);
418  return(status);
419}
420
421static unsigned long GetNumberChannels(const Image *image,
422  const ChannelType channel)
423{
424  unsigned long
425    channels;
426
427  channels=0;
428  if ((channel & RedChannel) != 0)
429    channels++;
430  if ((channel & GreenChannel) != 0)
431    channels++;
432  if ((channel & BlueChannel) != 0)
433    channels++;
434  if ((channel & OpacityChannel) != 0)
435    channels++;
436  if (((channel & IndexChannel) != 0) &&
437      (image->colorspace == CMYKColorspace))
438    channels++;
439  return(channels);
440}
441
442static MagickBooleanType GetMeanAbsoluteError(const Image *image,
443  const Image *reconstruct_image,const ChannelType channel,
444  double *distortion,ExceptionInfo *exception)
445{
446  double
447    scale;
448
449  long
450    y;
451
452  MagickBooleanType
453    status;
454
455  ViewInfo
456    *image_view,
457    *reconstruct_view;
458
459  status=MagickTrue;
460  scale=1.0/((double) image->columns*image->rows);
461  image_view=AcquireCacheView(image);
462  reconstruct_view=AcquireCacheView(reconstruct_image);
463  for (y=0; y < (long) image->rows; y++)
464  {
465    register const IndexPacket
466      *indexes,
467      *reconstruct_indexes;
468
469    register const PixelPacket
470      *p,
471      *q;
472
473    register long
474      x;
475
476    p=AcquireCacheViewPixels(image_view,0,y,image->columns,1,exception);
477    q=AcquireCacheViewPixels(reconstruct_view,0,y,reconstruct_image->columns,1,
478      exception);
479    if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
480      {
481        status=MagickFalse;
482        break;
483      }
484    indexes=AcquireCacheViewIndexes(image_view);
485    reconstruct_indexes=AcquireCacheViewIndexes(reconstruct_view);
486    for (x=0; x < (long) image->columns; x++)
487    {
488      MagickRealType
489        distance;
490
491      if ((channel & RedChannel) != 0)
492        {
493          distance=fabs(p->red-(double) q->red);
494          distortion[RedChannel]+=scale*distance;
495          distortion[AllChannels]+=scale*distance;
496        }
497      if ((channel & GreenChannel) != 0)
498        {
499          distance=fabs(p->green-(double) q->green);
500          distortion[GreenChannel]+=scale*distance;
501          distortion[AllChannels]+=scale*distance;
502        }
503      if ((channel & BlueChannel) != 0)
504        {
505          distance=fabs(p->blue-(double) q->blue);
506          distortion[BlueChannel]+=scale*distance;
507          distortion[AllChannels]+=scale*distance;
508        }
509      if ((channel & OpacityChannel) != 0)
510        {
511          distance=fabs(p->opacity-(double) q->opacity);
512          distortion[OpacityChannel]+=scale*distance;
513          distortion[AllChannels]+=scale*distance;
514        }
515      if (((channel & IndexChannel) != 0) &&
516          (image->colorspace == CMYKColorspace))
517        {
518          distance=fabs(indexes[x]-(double) reconstruct_indexes[x]);
519          distortion[BlackChannel]+=scale*distance;
520          distortion[AllChannels]+=scale*distance;
521        }
522      p++;
523      q++;
524    }
525  }
526  reconstruct_view=DestroyCacheView(reconstruct_view);
527  image_view=DestroyCacheView(image_view);
528  distortion[AllChannels]/=(double) GetNumberChannels(image,channel);
529  return(status);
530}
531
532static MagickBooleanType GetMeanErrorPerPixel(Image *image,
533  const Image *reconstruct_image,const ChannelType channel,double *distortion,
534  ExceptionInfo *exception)
535{
536  long
537    y;
538
539  MagickBooleanType
540    status;
541
542  MagickRealType
543    alpha,
544    area,
545    beta,
546    maximum_error,
547    mean_error;
548
549  ViewInfo
550    *image_view,
551    *reconstruct_view;
552
553  status=MagickTrue;
554  alpha=1.0;
555  beta=1.0;
556  area=0.0;
557  maximum_error=0.0;
558  mean_error=0.0;
559  image_view=AcquireCacheView(image);
560  reconstruct_view=AcquireCacheView(reconstruct_image);
561  for (y=0; y < (long) image->rows; y++)
562  {
563    register const IndexPacket
564      *indexes,
565      *reconstruct_indexes;
566
567    register const PixelPacket
568      *p,
569      *q;
570
571    register long
572      x;
573
574    p=AcquireCacheViewPixels(image_view,0,y,image->columns,1,exception);
575    q=AcquireCacheViewPixels(reconstruct_view,0,y,reconstruct_image->columns,1,
576      exception);
577    if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
578      {
579        status=MagickFalse;
580        break;
581      }
582    indexes=AcquireCacheViewIndexes(image_view);
583    reconstruct_indexes=AcquireCacheViewIndexes(reconstruct_view);
584    for (x=0; x < (long) image->columns; x++)
585    {
586      MagickRealType
587        distance;
588
589      if ((channel & OpacityChannel) != 0)
590        {
591          if (image->matte != MagickFalse)
592            alpha=(MagickRealType) (QuantumScale*(QuantumRange-p->opacity));
593          if (reconstruct_image->matte != MagickFalse)
594            beta=(MagickRealType) (QuantumScale*(QuantumRange-q->opacity));
595        }
596      if ((channel & RedChannel) != 0)
597        {
598          distance=fabs(alpha*p->red-beta*q->red);
599          distortion[RedChannel]+=distance;
600          distortion[AllChannels]+=distance;
601          mean_error+=distance*distance;
602          if (distance > maximum_error)
603            maximum_error=distance;
604          area++;
605        }
606      if ((channel & GreenChannel) != 0)
607        {
608          distance=fabs(alpha*p->green-beta*q->green);
609          distortion[GreenChannel]+=distance;
610          distortion[AllChannels]+=distance;
611          mean_error+=distance*distance;
612          if (distance > maximum_error)
613            maximum_error=distance;
614          area++;
615        }
616      if ((channel & BlueChannel) != 0)
617        {
618          distance=fabs(alpha*p->blue-beta*q->blue);
619          distortion[BlueChannel]+=distance;
620          distortion[AllChannels]+=distance;
621          mean_error+=distance*distance;
622          if (distance > maximum_error)
623            maximum_error=distance;
624          area++;
625        }
626      if ((channel & OpacityChannel) != 0)
627        {
628          distance=fabs(alpha*p->opacity-beta*q->opacity);
629          distortion[OpacityChannel]+=distance;
630          distortion[AllChannels]+=distance;
631          mean_error+=distance*distance;
632          if (distance > maximum_error)
633            maximum_error=distance;
634          area++;
635        }
636      if (((channel & IndexChannel) != 0) &&
637          (image->colorspace == CMYKColorspace) &&
638          (reconstruct_image->colorspace == CMYKColorspace))
639        {
640          distance=fabs(alpha*indexes[x]-beta*reconstruct_indexes[x]);
641          distortion[BlackChannel]+=distance;
642          distortion[AllChannels]+=distance;
643          mean_error+=distance*distance;
644          if (distance > maximum_error)
645            maximum_error=distance;
646          area++;
647        }
648      p++;
649      q++;
650    }
651  }
652  reconstruct_view=DestroyCacheView(reconstruct_view);
653  image_view=DestroyCacheView(image_view);
654  image->error.mean_error_per_pixel=distortion[AllChannels]/area;
655  image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
656  image->error.normalized_maximum_error=QuantumScale*maximum_error;
657  return(status);
658}
659
660static MagickBooleanType GetMeanSquaredError(const Image *image,
661  const Image *reconstruct_image,const ChannelType channel,
662  double *distortion,ExceptionInfo *exception)
663{
664  double
665    scale;
666
667  long
668    y;
669
670  MagickBooleanType
671    status;
672
673  ViewInfo
674    *image_view,
675    *reconstruct_view;
676
677  status=MagickTrue;
678  scale=1.0/((double) image->columns*image->rows);
679  image_view=AcquireCacheView(image);
680  reconstruct_view=AcquireCacheView(reconstruct_image);
681  for (y=0; y < (long) image->rows; y++)
682  {
683    register const IndexPacket
684      *indexes,
685      *reconstruct_indexes;
686
687    register const PixelPacket
688      *p,
689      *q;
690
691    register long
692      x;
693
694    p=AcquireCacheViewPixels(image_view,0,y,image->columns,1,exception);
695    q=AcquireCacheViewPixels(reconstruct_view,0,y,reconstruct_image->columns,1,
696      exception);
697    if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
698      {
699        status=MagickFalse;
700        break;
701      }
702    indexes=AcquireCacheViewIndexes(image_view);
703    reconstruct_indexes=AcquireCacheViewIndexes(reconstruct_view);
704    for (x=0; x < (long) image->columns; x++)
705    {
706      MagickRealType
707        distance;
708
709      if ((channel & RedChannel) != 0)
710        {
711          distance=(p->red-(MagickRealType) q->red);
712          distortion[RedChannel]+=QuantumScale*scale*distance*distance;
713          distortion[AllChannels]+=QuantumScale*scale*distance*distance;
714        }
715      if ((channel & GreenChannel) != 0)
716        {
717          distance=(p->green-(MagickRealType) q->green);
718          distortion[GreenChannel]+=QuantumScale*scale*distance*distance;
719          distortion[AllChannels]+=QuantumScale*scale*distance*distance;
720        }
721      if ((channel & BlueChannel) != 0)
722        {
723          distance=(p->blue-(MagickRealType) q->blue);
724          distortion[BlueChannel]+=QuantumScale*scale*distance*distance;
725          distortion[AllChannels]+=QuantumScale*scale*distance*distance;
726        }
727      if ((channel & OpacityChannel) != 0)
728        {
729          distance=(p->opacity-(MagickRealType) q->opacity);
730          distortion[OpacityChannel]+=QuantumScale*scale*distance*distance;
731          distortion[AllChannels]+=QuantumScale*scale*distance*distance;
732        }
733      if (((channel & IndexChannel) != 0) &&
734          (image->colorspace == CMYKColorspace) &&
735          (reconstruct_image->colorspace == CMYKColorspace))
736        {
737          distance=(indexes[x]-(MagickRealType) reconstruct_indexes[x]);
738          distortion[BlackChannel]+=QuantumScale*scale*distance*distance;
739          distortion[AllChannels]+=QuantumScale*scale*distance*distance;
740        }
741      p++;
742      q++;
743    }
744  }
745  reconstruct_view=DestroyCacheView(reconstruct_view);
746  image_view=DestroyCacheView(image_view);
747  distortion[AllChannels]/=(double) GetNumberChannels(image,channel);
748  return(status);
749}
750
751static MagickBooleanType GetPeakAbsoluteError(const Image *image,
752  const Image *reconstruct_image,const ChannelType channel,
753  double *distortion,ExceptionInfo *exception)
754{
755  long
756    y;
757
758  MagickBooleanType
759    status;
760
761  ViewInfo
762    *image_view,
763    *reconstruct_view;
764
765  status=MagickTrue;
766  image_view=AcquireCacheView(image);
767  reconstruct_view=AcquireCacheView(reconstruct_image);
768  for (y=0; y < (long) image->rows; y++)
769  {
770    register const IndexPacket
771      *indexes,
772      *reconstruct_indexes;
773
774    register const PixelPacket
775      *p,
776      *q;
777
778    register long
779      x;
780
781    p=AcquireCacheViewPixels(image_view,0,y,image->columns,1,exception);
782    q=AcquireCacheViewPixels(reconstruct_view,0,y,reconstruct_image->columns,1,
783      exception);
784    if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
785      break;
786    indexes=AcquireCacheViewIndexes(image_view);
787    reconstruct_indexes=AcquireCacheViewIndexes(reconstruct_view);
788    for (x=0; x < (long) image->columns; x++)
789    {
790      MagickRealType
791        distance;
792
793      if ((channel & RedChannel) != 0)
794        {
795          distance=fabs(p->red-(double) q->red);
796          if (distance > distortion[RedChannel])
797            distortion[RedChannel]=distance;
798          if (distance > distortion[AllChannels])
799            distortion[AllChannels]=distance;
800        }
801      if ((channel & GreenChannel) != 0)
802        {
803          distance=fabs(p->green-(double) q->green);
804          if (distance > distortion[GreenChannel])
805            distortion[GreenChannel]=distance;
806          if (distance > distortion[AllChannels])
807            distortion[AllChannels]=distance;
808        }
809      if ((channel & BlueChannel) != 0)
810        {
811          distance=fabs(p->blue-(double) q->blue);
812          if (distance > distortion[BlueChannel])
813            distortion[BlueChannel]=distance;
814          if (distance > distortion[AllChannels])
815            distortion[AllChannels]=distance;
816        }
817      if ((channel & OpacityChannel) != 0)
818        {
819          distance=fabs(p->opacity-(double) q->opacity);
820          if (distance > distortion[OpacityChannel])
821            distortion[OpacityChannel]=distance;
822          if (distance > distortion[AllChannels])
823            distortion[AllChannels]=distance;
824        }
825      if (((channel & IndexChannel) != 0) &&
826          (image->colorspace == CMYKColorspace) &&
827          (reconstruct_image->colorspace == CMYKColorspace))
828        {
829          distance=fabs(indexes[x]-(double) reconstruct_indexes[x]);
830          if (distance > distortion[BlackChannel])
831            distortion[BlackChannel]=distance;
832          if (distance > distortion[AllChannels])
833            distortion[AllChannels]=distance;
834        }
835      p++;
836      q++;
837    }
838  }
839  reconstruct_view=DestroyCacheView(reconstruct_view);
840  image_view=DestroyCacheView(image_view);
841  return(status);
842}
843
844static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
845  const Image *reconstruct_image,const ChannelType channel,
846  double *distortion,ExceptionInfo *exception)
847{
848  MagickBooleanType
849    status;
850
851  status=GetMeanSquaredError(image,reconstruct_image,channel,distortion,
852    exception);
853  if ((channel & RedChannel) != 0)
854    distortion[RedChannel]=20.0*log10((double) QuantumRange/
855      sqrt(distortion[RedChannel]));
856  if ((channel & GreenChannel) != 0)
857    distortion[GreenChannel]=20.0*log10((double) QuantumRange/
858      sqrt(distortion[GreenChannel]));
859  if ((channel & BlueChannel) != 0)
860    distortion[BlueChannel]=20.0*log10((double) QuantumRange/
861      sqrt(distortion[BlueChannel]));
862  if ((channel & OpacityChannel) != 0)
863    distortion[OpacityChannel]=20.0*log10((double) QuantumRange/
864      sqrt(distortion[OpacityChannel]));
865  if (((channel & IndexChannel) != 0) &&
866      (image->colorspace == CMYKColorspace))
867    distortion[BlackChannel]=20.0*log10((double) QuantumRange/
868      sqrt(distortion[BlackChannel]));
869  distortion[AllChannels]=20.0*log10((double) QuantumRange/
870    sqrt((double) distortion[AllChannels]));
871  return(status);
872}
873
874static MagickBooleanType GetRootMeanSquaredError(const Image *image,
875  const Image *reconstruct_image,const ChannelType channel,
876  double *distortion,ExceptionInfo *exception)
877{
878  MagickBooleanType
879    status;
880
881  status=GetMeanSquaredError(image,reconstruct_image,channel,distortion,
882    exception);
883  if ((channel & RedChannel) != 0)
884    distortion[RedChannel]=sqrt(distortion[RedChannel]);
885  if ((channel & GreenChannel) != 0)
886    distortion[GreenChannel]=sqrt(distortion[GreenChannel]);
887  if ((channel & BlueChannel) != 0)
888    distortion[BlueChannel]=sqrt(distortion[BlueChannel]);
889  if ((channel & OpacityChannel) != 0)
890    distortion[OpacityChannel]=sqrt(distortion[OpacityChannel]);
891  if (((channel & IndexChannel) != 0) &&
892      (image->colorspace == CMYKColorspace))
893    distortion[BlackChannel]=sqrt(distortion[BlackChannel]);
894  distortion[AllChannels]=sqrt(distortion[AllChannels]);
895  return(status);
896}
897
898MagickExport MagickBooleanType GetImageChannelDistortion(Image *image,
899  const Image *reconstruct_image,const ChannelType channel,
900  const MetricType metric,double *distortion,ExceptionInfo *exception)
901{
902  double
903    *channel_distortion;
904
905  MagickBooleanType
906    status;
907
908  size_t
909    length;
910
911  assert(image != (Image *) NULL);
912  assert(image->signature == MagickSignature);
913  if (image->debug != MagickFalse)
914    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
915  assert(reconstruct_image != (const Image *) NULL);
916  assert(reconstruct_image->signature == MagickSignature);
917  assert(distortion != (double *) NULL);
918  *distortion=0.0;
919  if (image->debug != MagickFalse)
920    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
921  if ((reconstruct_image->columns != image->columns) ||
922      (reconstruct_image->rows != image->rows))
923    ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
924  /*
925    Get image distortion.
926  */
927  length=AllChannels+1UL;
928  channel_distortion=(double *) AcquireQuantumMemory(length,
929    sizeof(*channel_distortion));
930  if (channel_distortion == (double *) NULL)
931    ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
932  (void) ResetMagickMemory(channel_distortion,0,length*
933    sizeof(*channel_distortion));
934  switch (metric)
935  {
936    case AbsoluteErrorMetric:
937    {
938      status=GetAbsoluteError(image,reconstruct_image,channel,
939        channel_distortion,exception);
940      break;
941    }
942    case MeanAbsoluteErrorMetric:
943    {
944      status=GetMeanAbsoluteError(image,reconstruct_image,channel,
945        channel_distortion,exception);
946      break;
947    }
948    case MeanErrorPerPixelMetric:
949    {
950      status=GetMeanErrorPerPixel(image,reconstruct_image,channel,
951        channel_distortion,exception);
952      break;
953    }
954    case MeanSquaredErrorMetric:
955    {
956      status=GetMeanSquaredError(image,reconstruct_image,channel,
957        channel_distortion,exception);
958      break;
959    }
960    case PeakAbsoluteErrorMetric:
961    default:
962    {
963      status=GetPeakAbsoluteError(image,reconstruct_image,channel,
964        channel_distortion,exception);
965      break;
966    }
967    case PeakSignalToNoiseRatioMetric:
968    {
969      status=GetPeakSignalToNoiseRatio(image,reconstruct_image,channel,
970        channel_distortion,exception);
971      break;
972    }
973    case RootMeanSquaredErrorMetric:
974    {
975      status=GetRootMeanSquaredError(image,reconstruct_image,channel,
976        channel_distortion,exception);
977      break;
978    }
979  }
980  *distortion=channel_distortion[AllChannels];
981  channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
982  return(status);
983}
984
985/*
986%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
987%                                                                             %
988%                                                                             %
989%                                                                             %
990%   G e t I m a g e C h a n n e l D i s t o r t i o n s                       %
991%                                                                             %
992%                                                                             %
993%                                                                             %
994%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
995%
996%  GetImageChannelDistrortion() compares the image channels of an image to a
997%  reconstructed image and returns the specified distortion metric for each
998%  channel.
999%
1000%  The format of the CompareImageChannels method is:
1001%
1002%      double *GetImageChannelDistortions(const Image *image,
1003%        const Image *reconstruct_image,const MetricType metric,
1004%        ExceptionInfo *exception)
1005%
1006%  A description of each parameter follows:
1007%
1008%    o image: the image.
1009%
1010%    o reconstruct_image: the reconstruct image.
1011%
1012%    o metric: the metric.
1013%
1014%    o exception: Return any errors or warnings in this structure.
1015%
1016*/
1017MagickExport double *GetImageChannelDistortions(Image *image,
1018  const Image *reconstruct_image,const MetricType metric,
1019  ExceptionInfo *exception)
1020{
1021  double
1022    *channel_distortion;
1023
1024  MagickBooleanType
1025    status;
1026
1027  size_t
1028    length;
1029
1030  assert(image != (Image *) NULL);
1031  assert(image->signature == MagickSignature);
1032  if (image->debug != MagickFalse)
1033    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1034  assert(reconstruct_image != (const Image *) NULL);
1035  assert(reconstruct_image->signature == MagickSignature);
1036  if (image->debug != MagickFalse)
1037    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1038  if ((reconstruct_image->columns != image->columns) ||
1039      (reconstruct_image->rows != image->rows))
1040    {
1041      (void) ThrowMagickException(&image->exception,GetMagickModule(),
1042        ImageError,"ImageSizeDiffers","`%s'",image->filename);
1043      return((double *) NULL);
1044    }
1045  /*
1046    Get image distortion.
1047  */
1048  length=AllChannels+1UL;
1049  channel_distortion=(double *) AcquireQuantumMemory(length,
1050    sizeof(*channel_distortion));
1051  if (channel_distortion == (double *) NULL)
1052    ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1053  (void) ResetMagickMemory(channel_distortion,0,length*
1054    sizeof(*channel_distortion));
1055  switch (metric)
1056  {
1057    case AbsoluteErrorMetric:
1058    {
1059      status=GetAbsoluteError(image,reconstruct_image,AllChannels,
1060        channel_distortion,exception);
1061      break;
1062    }
1063    case