Coverage Report

Created: 2026-04-01 07:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/graphicsmagick/magick/compare.c
Line
Count
Source
1
/*
2
% Copyright (C) 2003-2026 GraphicsMagick Group
3
% Copyright (C) 2002 ImageMagick Studio
4
% Copyright 1991-1999 E. I. du Pont de Nemours and Company
5
%
6
% This program is covered by multiple licenses, which are described in
7
% Copyright.txt. You should have received a copy of Copyright.txt with this
8
% package; otherwise see http://www.graphicsmagick.org/www/Copyright.html.
9
%
10
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
11
%                                                                             %
12
%                                                                             %
13
%                                                                             %
14
%               CCCC   OOO   M   M  PPPP    AAA   RRRR    EEEEE               %
15
%              C      O   O  MM MM  P   P  A   A  R   R   E                   %
16
%              C      O   O  M M M  PPPP   AAAAA  RRRR    EEE                 %
17
%              C      O   O  M   M  P      A   A  R R     E                   %
18
%               CCCC   OOO   M   M  P      A   A  R  R    EEEEE               %
19
%                                                                             %
20
%                                                                             %
21
%                    GraphicsMagick Image Compare Methods                     %
22
%                                                                             %
23
%                                                                             %
24
%                                                                             %
25
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
26
%
27
%
28
%
29
*/
30

31
/*
32
  Include declarations.
33
*/
34
#include "magick/studio.h"
35
#include "magick/alpha_composite.h"
36
#include "magick/color.h"
37
#include "magick/color_lookup.h"
38
#include "magick/compare.h"
39
#include "magick/enum_strings.h"
40
#include "magick/pixel_iterator.h"
41
#include "magick/utility.h"
42

43
/*
44
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
45
%                                                                             %
46
%                                                                             %
47
%                                                                             %
48
%   D i f f e r e n c e I m a g e                                             %
49
%                                                                             %
50
%                                                                             %
51
%                                                                             %
52
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
53
%
54
%  DifferenceImage() returns an annotated difference image based on the
55
%  the difference between a reference image and a compare image.
56
%
57
%  The format of the DifferenceImage method is:
58
%
59
%      Image *DifferenceImage(const Image *reference_image,
60
%                             const Image *compare_image,
61
%                             const DifferenceImageOptions *difference_options,
62
%                             ExceptionInfo *exception)
63
%
64
%  A description of each parameter follows:
65
%
66
%    o reference_image: the reference image.
67
%
68
%    o compare_image: the comparison image.
69
%
70
%    o difference_options: options to use when differencing.
71
%
72
%    o channel: the channel(s) to compare.
73
%
74
%    o exception: Return any errors or warnings in this structure.
75
%
76
*/
77
static MagickPassFail
78
DifferenceImagePixels(void *mutable_data,                  /* User provided mutable data */
79
                      const void *immutable_data,          /* User provided immutable data */
80
                      const Image * restrict reference_image,        /* Source 1 image */
81
                      const PixelPacket * restrict reference_pixels, /* Pixel row in source 1 image */
82
                      const IndexPacket * restrict reference_indexes,/* Pixel row indexes in source 1 image */
83
                      const Image * restrict compare_image,          /* Source 2 image */
84
                      const PixelPacket * restrict compare_pixels, /* Pixel row in source 2 image */
85
                      const IndexPacket * restrict compare_indexes, /* Pixel row indexes in source 2 image */
86
                      Image * restrict result_image,                 /* Update image */
87
                      PixelPacket * restrict result_pixels, /* Pixel row in update image */
88
                      IndexPacket * restrict result_indexes, /* Pixel row indexes in update image */
89
                      const long npixels,                  /* Number of pixels in row */
90
                      ExceptionInfo *exception             /* Exception report */
91
                   )
92
0
{
93
0
  const DifferenceImageOptions
94
0
    *difference_options = (const DifferenceImageOptions *) immutable_data;
95
96
0
  register ChannelType
97
0
    channels = difference_options->channel;
98
99
0
  register long
100
0
    i;
101
102
0
  register MagickBool
103
0
    change;
104
105
0
  ARG_NOT_USED(mutable_data);
106
0
  ARG_NOT_USED(compare_image);
107
0
  ARG_NOT_USED(result_image);
108
0
  ARG_NOT_USED(result_indexes);
109
0
  ARG_NOT_USED(exception);
110
111
0
  for (i=0; i < npixels; i++)
112
0
    {
113
0
      change=MagickFalse;
114
115
0
      if (IsCMYKColorspace(reference_image->colorspace))
116
0
        {
117
0
          if (MagickChannelEnabled(channels,CyanChannel) &&
118
0
              (GetCyanSample(&reference_pixels[i]) != GetCyanSample(&compare_pixels[i])))
119
0
            change=MagickTrue;
120
0
          if (MagickChannelEnabled(channels,MagentaChannel) &&
121
0
              (GetMagentaSample(&reference_pixels[i]) != GetMagentaSample(&compare_pixels[i])))
122
0
            change=MagickTrue;
123
0
          if (MagickChannelEnabled(channels,YellowChannel) &&
124
0
              (GetYellowSample(&reference_pixels[i]) != GetYellowSample(&compare_pixels[i])))
125
0
            change=MagickTrue;
126
0
          if (MagickChannelEnabled(channels,BlackChannel) &&
127
0
              (GetBlackSample(&reference_pixels[i]) != GetBlackSample(&compare_pixels[i])))
128
0
            change=MagickTrue;
129
0
          if (MagickChannelEnabled(channels,OpacityChannel) &&
130
0
              (reference_indexes[i] != compare_indexes[i]))
131
0
            change=MagickTrue;
132
0
        }
133
0
      else
134
0
        {
135
0
          if (MagickChannelEnabled(channels,RedChannel) &&
136
0
              (GetRedSample(&reference_pixels[i]) != GetRedSample(&compare_pixels[i])))
137
0
            change=MagickTrue;
138
0
          if (MagickChannelEnabled(channels,GreenChannel) &&
139
0
              (GetGreenSample(&reference_pixels[i]) != GetGreenSample(&compare_pixels[i])))
140
0
            change=MagickTrue;
141
0
          if (MagickChannelEnabled(channels,BlueChannel) &&
142
0
              (GetBlueSample(&reference_pixels[i]) != GetBlueSample(&compare_pixels[i])))
143
0
            change=MagickTrue;
144
0
          if (MagickChannelEnabled(channels,OpacityChannel) &&
145
0
              (GetOpacitySample(&reference_pixels[i]) != GetOpacitySample(&compare_pixels[i])))
146
0
            change=MagickTrue;
147
0
        }
148
      /*
149
        Modify result image to reflect change.
150
      */
151
0
      switch (difference_options->highlight_style)
152
0
        {
153
0
        case UndefinedHighlightStyle:
154
0
          break;
155
0
        case AssignHighlightStyle:
156
0
          {
157
            /*
158
              Changed pixels are assigned the highlight color.
159
            */
160
0
            if (change)
161
0
              result_pixels[i]=difference_options->highlight_color;
162
0
            else
163
0
              result_pixels[i]=compare_pixels[i];
164
0
            break;
165
0
          }
166
0
        case ThresholdHighlightStyle:
167
0
          {
168
            /*
169
              For changed pixels, compare the pixel intensity.  If the
170
              pixel intensity in the compare image is higher than the
171
              reference image, then set the pixel to white, otherwise
172
              set it to black.
173
            */
174
0
            if (change)
175
0
              {
176
0
                Quantum
177
0
                  compare_intensity,
178
0
                  intensity,
179
0
                  reference_intensity;
180
181
0
                compare_intensity=PixelIntensity(&compare_pixels[i]);
182
0
                reference_intensity=PixelIntensity(&reference_pixels[i]);
183
0
                if (compare_intensity > reference_intensity)
184
0
                  intensity=MaxRGB;
185
0
                else
186
0
                  intensity=0U;
187
0
                result_pixels[i].red = result_pixels[i].green = result_pixels[i].blue = intensity;
188
0
                result_pixels[i].opacity=compare_pixels[i].opacity;
189
0
              }
190
0
            else
191
0
              {
192
0
                result_pixels[i]=compare_pixels[i];
193
0
              }
194
0
            break;
195
0
          }
196
0
        case TintHighlightStyle:
197
0
          {
198
            /*
199
              Alpha composite highlight color on top of change pixels.
200
            */
201
0
            if (change)
202
0
              AlphaCompositePixel(&result_pixels[i],&difference_options->highlight_color,0.75*MaxRGBDouble,
203
0
                                  &compare_pixels[i],compare_pixels[i].opacity);
204
0
            else
205
0
              result_pixels[i]=compare_pixels[i];
206
0
            break;
207
0
          }
208
0
        case XorHighlightStyle:
209
0
          {
210
0
            if (change)
211
0
              {
212
0
                result_pixels[i].red = compare_pixels[i].red ^ difference_options->highlight_color.red;
213
0
                result_pixels[i].green = compare_pixels[i].green ^ difference_options->highlight_color.green;
214
0
                result_pixels[i].blue = compare_pixels[i].blue ^ difference_options->highlight_color.blue;
215
0
                result_pixels[i].opacity = compare_pixels[i].opacity ^ difference_options->highlight_color.opacity;
216
0
              }
217
0
            else
218
0
              {
219
0
                result_pixels[i]=compare_pixels[i];
220
0
              }
221
0
            break;
222
0
          }
223
0
        }
224
0
    }
225
226
0
  return MagickPass;
227
0
}
228
229
MagickExport Image *
230
DifferenceImage(const Image *reference_image,const Image *compare_image,
231
                const DifferenceImageOptions *difference_options,
232
                ExceptionInfo *exception)
233
0
{
234
0
  Image
235
0
    *difference_image;
236
237
0
  assert(reference_image != (const Image *) NULL);
238
0
  assert(reference_image->signature == MagickSignature);
239
0
  assert(compare_image != (const Image *) NULL);
240
0
  assert(compare_image->signature == MagickSignature);
241
0
  assert(difference_options != (const DifferenceImageOptions *) NULL);
242
0
  assert(exception != (ExceptionInfo *) NULL);
243
244
0
  difference_image=AllocateImage((ImageInfo *) NULL);
245
0
  if (difference_image == (Image *) NULL)
246
0
    {
247
0
      ThrowImageException3(ResourceLimitError,MemoryAllocationFailed,
248
0
                           UnableToAllocateImage);
249
0
      return ((Image *) NULL);
250
0
    }
251
0
  difference_image->storage_class = DirectClass;
252
0
  difference_image->rows = reference_image->rows;
253
0
  difference_image->columns = reference_image->columns;
254
0
  difference_image->depth = Max(reference_image->depth, compare_image->depth);
255
256
  /*
257
    Update "difference" image to mark changes.
258
  */
259
0
  (void) PixelIterateTripleModify(DifferenceImagePixels,
260
0
                                  NULL,
261
0
                                  "[%s]*[%s]->[%s] Difference image pixels ...",
262
0
                                  NULL,difference_options,
263
0
                                  reference_image->columns,reference_image->rows,
264
0
                                  reference_image, compare_image,0, 0,
265
0
                                  difference_image, 0, 0,
266
0
                                  exception);
267
0
  return difference_image;
268
0
}
269

270
/*
271
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
272
%                                                                             %
273
%                                                                             %
274
%                                                                             %
275
%   G e t I m a g e C h a n n e l D i f f e r e n c e                         %
276
%                                                                             %
277
%                                                                             %
278
%                                                                             %
279
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
280
%
281
%  GetImageChannelDifference() updates a user provided statistics structure
282
%  with per-channel, and totalized, difference statistics corresponding
283
%  to a specified comparison metric.
284
%
285
%  The format of the GetImageChannelDifference method is:
286
%
287
%      MagickPassFail GetImageChannelDifference(const Image *reference_image,
288
%                                           const Image *compare_image,
289
%                                           const MetricType metric,
290
%                                           DifferenceStatistics *statistics,
291
%                                           ExceptionInfo *exception)
292
%
293
%  A description of each parameter follows:
294
%
295
%    o reference_image: the reference image.
296
%
297
%    o compare_image: the comparison image.
298
%
299
%    o metric: metric to use when differencing.
300
%
301
%    o statistics: the statistics structure to populate.
302
%
303
%    o exception: Return any errors or warnings in this structure.
304
%
305
*/
306
/*
307
  Compute the total absolute value difference.
308
309
  In this case we sum the absolute value difference between channel
310
  pixel quantums.
311
*/
312
static MagickPassFail
313
ComputeAbsoluteError(void *mutable_data,
314
                     const void *immutable_data,
315
                     const Image * restrict first_image,
316
                     const PixelPacket * restrict first_pixels,
317
                     const IndexPacket * restrict first_indexes,
318
                     const Image * restrict second_image,
319
                     const PixelPacket * restrict second_pixels,
320
                     const IndexPacket * restrict second_indexes,
321
                     const long npixels,
322
                     ExceptionInfo *exception)
323
0
{
324
0
  DifferenceStatistics
325
0
    lstats,
326
0
    *stats = (DifferenceStatistics *) mutable_data;
327
328
0
  register long
329
0
    i;
330
331
0
  ARG_NOT_USED(immutable_data);
332
0
  ARG_NOT_USED(first_image);
333
0
  ARG_NOT_USED(first_indexes);
334
0
  ARG_NOT_USED(second_image);
335
0
  ARG_NOT_USED(second_indexes);
336
0
  ARG_NOT_USED(exception);
337
338
0
  InitializeDifferenceStatistics(&lstats,exception);
339
0
  for (i=0; i < npixels; i++)
340
0
    {
341
0
      lstats.red += fabs(first_pixels[i].red-(double) second_pixels[i].red)/MaxRGBDouble;
342
0
      lstats.green += fabs(first_pixels[i].green-(double) second_pixels[i].green)/MaxRGBDouble;
343
0
      lstats.blue += fabs(first_pixels[i].blue-(double) second_pixels[i].blue)/MaxRGBDouble;
344
0
      lstats.opacity += fabs(first_pixels[i].opacity-(double) second_pixels[i].opacity)/MaxRGBDouble;
345
0
    }
346
347
#if defined(HAVE_OPENMP)
348
#  pragma omp critical (GM_ComputeAbsoluteError)
349
#endif
350
0
  {
351
0
    stats->red += lstats.red;
352
0
    stats->green += lstats.green;
353
0
    stats->blue += lstats.blue;
354
0
    stats->opacity += lstats.opacity;
355
0
  }
356
357
0
  return (MagickPass);
358
0
}
359
360
/*
361
  Compute the peak absolute difference.
362
363
  In this case we compute the simple difference between channel pixel
364
  quantums, obtain the absolute value, and store the value if it is
365
  greater than the current peak value.
366
*/
367
static MagickPassFail
368
ComputePeakAbsoluteError(void *mutable_data,
369
                         const void *immutable_data,
370
                         const Image * restrict first_image,
371
                         const PixelPacket * restrict first_pixels,
372
                         const IndexPacket * restrict first_indexes,
373
                         const Image * restrict second_image,
374
                         const PixelPacket * restrict second_pixels,
375
                         const IndexPacket * restrict second_indexes,
376
                         const long npixels,
377
                         ExceptionInfo *exception)
378
0
{
379
0
  DifferenceStatistics
380
0
    lstats,
381
0
    *stats = (DifferenceStatistics *) mutable_data;
382
383
0
  double
384
0
    difference;
385
386
0
  register long
387
0
    i;
388
389
0
  ARG_NOT_USED(immutable_data);
390
0
  ARG_NOT_USED(first_image);
391
0
  ARG_NOT_USED(first_indexes);
392
0
  ARG_NOT_USED(second_image);
393
0
  ARG_NOT_USED(second_indexes);
394
395
0
  InitializeDifferenceStatistics(&lstats,exception);
396
0
  for (i=0; i < npixels; i++)
397
0
    {
398
0
      difference=fabs(first_pixels[i].red-(double) second_pixels[i].red)/MaxRGBDouble;
399
0
      if (difference > lstats.red)
400
0
        lstats.red=difference;
401
402
0
      difference=fabs(first_pixels[i].green-(double) second_pixels[i].green)/MaxRGBDouble;
403
0
      if (difference > lstats.green)
404
0
        lstats.green=difference;
405
406
0
      difference=fabs(first_pixels[i].blue-(double) second_pixels[i].blue)/MaxRGBDouble;
407
0
      if (difference > lstats.blue)
408
0
        lstats.blue=difference;
409
410
0
      difference=fabs(first_pixels[i].opacity-(double) second_pixels[i].opacity)/MaxRGBDouble;
411
0
      if (difference > lstats.opacity)
412
0
        lstats.opacity=difference;
413
0
    }
414
415
#if defined(HAVE_OPENMP)
416
#  pragma omp critical (GM_ComputePeakAbsoluteError)
417
#endif
418
0
  {
419
0
    if (lstats.red > stats->red)
420
0
      stats->red=lstats.red;
421
0
    if (lstats.green > stats->green)
422
0
      stats->green=lstats.green;
423
0
    if (lstats.blue > stats->blue)
424
0
      stats->blue=lstats.blue;
425
0
    if (lstats.opacity > stats->opacity)
426
0
      stats->opacity=lstats.opacity;
427
0
  }
428
429
0
  return (MagickPass);
430
0
}
431
432
/*
433
  Compute the squared difference.
434
435
  In this case we sum the square of the difference between channel
436
  pixel quantums.
437
*/
438
static MagickPassFail
439
ComputeSquaredError(void *mutable_data,
440
                    const void *immutable_data,
441
                    const Image * restrict first_image,
442
                    const PixelPacket * restrict first_pixels,
443
                    const IndexPacket * restrict first_indexes,
444
                    const Image * restrict second_image,
445
                    const PixelPacket * restrict second_pixels,
446
                    const IndexPacket * restrict second_indexes,
447
                    const long npixels,
448
                    ExceptionInfo *exception)
449
0
{
450
0
  DifferenceStatistics
451
0
    lstats,
452
0
    *stats = (DifferenceStatistics *) mutable_data;
453
454
0
  double
455
0
    difference;
456
457
0
  register long
458
0
    i;
459
460
0
  ARG_NOT_USED(immutable_data);
461
0
  ARG_NOT_USED(first_image);
462
0
  ARG_NOT_USED(first_indexes);
463
0
  ARG_NOT_USED(second_image);
464
0
  ARG_NOT_USED(second_indexes);
465
0
  ARG_NOT_USED(exception);
466
467
0
  InitializeDifferenceStatistics(&lstats,exception);
468
0
  for (i=0; i < npixels; i++)
469
0
    {
470
0
      difference=(first_pixels[i].red-(double) second_pixels[i].red)/MaxRGBDouble;
471
0
      lstats.red += difference*difference;
472
473
0
      difference=(first_pixels[i].green-(double) second_pixels[i].green)/MaxRGBDouble;
474
0
      lstats.green += difference*difference;
475
476
0
      difference=(first_pixels[i].blue-(double) second_pixels[i].blue)/MaxRGBDouble;
477
0
      lstats.blue += difference*difference;
478
479
0
      difference=(first_pixels[i].opacity-(double) second_pixels[i].opacity)/MaxRGBDouble;
480
0
      lstats.opacity += difference*difference;
481
0
    }
482
483
#if defined(HAVE_OPENMP)
484
#  pragma omp critical (GM_ComputeSquaredError)
485
#endif
486
0
  {
487
0
    stats->red += lstats.red;
488
0
    stats->green += lstats.green;
489
0
    stats->blue += lstats.blue;
490
0
    stats->opacity += lstats.opacity;
491
0
  }
492
493
0
  return (MagickPass);
494
0
}
495
MagickExport MagickPassFail
496
GetImageChannelDifference(const Image *reference_image,
497
                          const Image *compare_image,
498
                          const MetricType metric,
499
                          DifferenceStatistics *statistics,
500
                          ExceptionInfo *exception)
501
0
{
502
0
  PixelIteratorDualReadCallback
503
0
    call_back = (PixelIteratorDualReadCallback) NULL;
504
505
0
  MagickPassFail
506
0
    status = MagickFail;
507
508
0
  assert(reference_image != (const Image *) NULL);
509
0
  assert(reference_image->signature == MagickSignature);
510
0
  assert(compare_image != (const Image *) NULL);
511
0
  assert(compare_image->signature == MagickSignature);
512
0
  assert(statistics != (DifferenceStatistics *) NULL);
513
0
  assert(exception != (ExceptionInfo *) NULL);
514
515
0
  InitializeDifferenceStatistics(statistics,exception);
516
517
  /*
518
    Select basic differencing function to use.
519
  */
520
0
  switch (metric)
521
0
    {
522
0
    case UndefinedMetric:
523
0
      break;
524
0
    case MeanAbsoluteErrorMetric:
525
0
      call_back=ComputeAbsoluteError;
526
0
      break;
527
0
    case MeanSquaredErrorMetric:
528
0
      call_back=ComputeSquaredError;
529
0
      break;
530
0
    case PeakAbsoluteErrorMetric:
531
0
      call_back=ComputePeakAbsoluteError;
532
0
      break;
533
0
    case PeakSignalToNoiseRatioMetric:
534
0
      call_back=ComputeSquaredError;
535
0
      break;
536
0
    case RootMeanSquaredErrorMetric:
537
0
      call_back=ComputeSquaredError;
538
0
      break;
539
0
    }
540
541
0
  if (call_back != (PixelIteratorDualReadCallback) NULL)
542
0
    {
543
0
      double
544
0
        number_channels,
545
0
        number_pixels;
546
547
0
      char
548
0
        description[MaxTextExtent];
549
550
0
      MagickFormatString(description,sizeof(description),
551
0
                         "[%%s]*[%%s] Compute image difference using %s metric...",
552
0
                         MetricTypeToString(metric));
553
554
0
      status=PixelIterateDualRead(call_back,
555
0
                                  NULL,
556
0
                                  description,
557
0
                                  statistics, NULL,
558
0
                                  reference_image->columns,reference_image->rows,
559
0
                                  reference_image,0,0,
560
0
                                  compare_image,0,0,
561
0
                                  exception);
562
      /*
563
        Post-process statistics (as required)
564
      */
565
566
0
      number_channels=3.0 + (reference_image->matte ? 1.0 : 0.0);
567
0
      number_pixels=(double) reference_image->columns*reference_image->rows;
568
569
0
      if ((MeanAbsoluteErrorMetric == metric) ||
570
0
          (MeanSquaredErrorMetric == metric) ||
571
0
          (PeakSignalToNoiseRatioMetric == metric)||
572
0
          (RootMeanSquaredErrorMetric == metric))
573
0
        {
574
          /*
575
            Compute mean values.
576
          */
577
0
          statistics->combined=((statistics->red+statistics->green+
578
0
                                 statistics->blue+
579
0
                                 (reference_image->matte ? statistics->opacity : 0.0))/
580
0
                                (number_pixels*number_channels));
581
0
          statistics->red /= number_pixels;
582
0
          statistics->green /= number_pixels;
583
0
          statistics->blue /= number_pixels;
584
0
          statistics->opacity /= number_pixels;
585
0
        }
586
587
0
      if (PeakAbsoluteErrorMetric == metric)
588
0
        {
589
          /*
590
            Determine peak channel value
591
          */
592
0
          if (statistics->red > statistics->combined)
593
0
            statistics->combined=statistics->red;
594
595
0
          if (statistics->green > statistics->combined)
596
0
            statistics->combined=statistics->green;
597
598
0
          if (statistics->blue > statistics->combined)
599
0
            statistics->combined=statistics->blue;
600
601
0
          if ((reference_image->matte) && (statistics->opacity > statistics->combined))
602
0
            statistics->combined=statistics->opacity;
603
0
        }
604
605
0
      if (PeakSignalToNoiseRatioMetric == metric)
606
0
        {
607
          /*
608
            Compute PSNR.
609
          */
610
0
          statistics->red=(20.0 * log10(1.0/sqrt(statistics->red)));
611
0
          statistics->green=(20.0 * log10(1.0/sqrt(statistics->green)));
612
0
          statistics->blue=(20.0 * log10(1.0/sqrt(statistics->blue)));
613
0
          statistics->opacity=(20.0 * log10(1.0/sqrt(statistics->opacity)));
614
0
          statistics->combined=(20.0 * log10(1.0/sqrt(statistics->combined)));
615
0
        }
616
617
0
      if (RootMeanSquaredErrorMetric == metric)
618
0
        {
619
          /*
620
            Compute RMSE.
621
          */
622
0
          statistics->red=sqrt(statistics->red);
623
0
          statistics->green=sqrt(statistics->green);
624
0
          statistics->blue=sqrt(statistics->blue);
625
0
          statistics->opacity=sqrt(statistics->opacity);
626
0
          statistics->combined=sqrt(statistics->combined);
627
0
        }
628
0
    }
629
630
0
  return status;
631
0
}
632

633
/*
634
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
635
%                                                                             %
636
%                                                                             %
637
%                                                                             %
638
%   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                         %
639
%                                                                             %
640
%                                                                             %
641
%                                                                             %
642
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
643
%
644
%  GetImageChannelDistortion() updates a distortion parameter with the
645
%  distortion (error) computed according to the specified comparison metric.
646
%  The value returned is only for the channel specified.
647
%
648
%  The format of the GetImageChannelDistortion method is:
649
%
650
%       MagickPassFail GetImageChannelDistortion(const Image *reference_image,
651
%                                                const Image *compare_image,
652
%                                                const ChannelType channel,
653
%                                                const MetricType metric,
654
%                                                double *distortion,
655
%                                                ExceptionInfo *exception)
656
%
657
%  A description of each parameter follows:
658
%
659
%    o reference_image: the reference image.
660
%
661
%    o compare_image: the comparison image.
662
%
663
%    o channel: the channel to obtain error data for.
664
%
665
%    o metric: metric to use when differencing.
666
%
667
%    o distortion: updated with the computed distortion.
668
%
669
%    o exception: Return any errors or warnings in this structure.
670
%
671
*/
672
MagickExport MagickPassFail
673
GetImageChannelDistortion(const Image *reference_image,
674
                          const Image *compare_image,
675
                          const ChannelType channel,
676
                          const MetricType metric,
677
                          double *distortion,
678
                          ExceptionInfo *exception)
679
0
{
680
0
  DifferenceStatistics
681
0
    statistics;
682
683
0
  MagickPassFail
684
0
    status;
685
686
0
  assert(distortion != (double *) NULL);
687
688
0
  *distortion=1.0;
689
0
  status=GetImageChannelDifference(reference_image,compare_image,metric,
690
0
                                   &statistics,exception);
691
0
  switch (channel)
692
0
    {
693
0
    case RedChannel:
694
0
    case CyanChannel:
695
0
      *distortion=statistics.red;
696
0
      break;
697
0
    case GreenChannel:
698
0
    case MagentaChannel:
699
0
      *distortion=statistics.green;
700
0
      break;
701
0
    case BlueChannel:
702
0
    case YellowChannel:
703
0
      *distortion=statistics.blue;
704
0
      break;
705
0
    case BlackChannel:
706
0
    case MatteChannel:
707
0
    case OpacityChannel:
708
0
      *distortion=statistics.opacity;
709
0
      break;
710
0
    case UndefinedChannel:
711
0
    case AllChannels:
712
0
    case GrayChannel:
713
0
      *distortion=statistics.combined;
714
0
      break;
715
0
    }
716
717
0
    return status;
718
0
}
719

720
/*
721
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
722
%                                                                             %
723
%                                                                             %
724
%                                                                             %
725
%   G e t I m a g e D i s t o r t i o n                                       %
726
%                                                                             %
727
%                                                                             %
728
%                                                                             %
729
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
730
%
731
%  GetImageDistortion() updates a distortion parameter with the distortion
732
%  (error) computed according to the specified comparison metric.  The value
733
%  returned reflects all enabled channels.
734
%
735
%  The format of the GetImageDistortion method is:
736
%
737
%       MagickPassFail GetImageDistortion(const Image *reference_image,
738
%                                         const Image *compare_image,
739
%                                         const MetricType metric,
740
%                                         double *distortion,
741
%                                         ExceptionInfo *exception)
742
%
743
%  A description of each parameter follows:
744
%
745
%    o reference_image: the reference image.
746
%
747
%    o compare_image: the comparison image.
748
%
749
%    o channel: the channel to obtain error data for.
750
%
751
%    o metric: metric to use when differencing.
752
%
753
%    o distortion: updated with the computed distortion.
754
%
755
%    o exception: Return any errors or warnings in this structure.
756
%
757
*/
758
MagickExport MagickPassFail
759
GetImageDistortion(const Image *reference_image,
760
                   const Image *compare_image,
761
                   const MetricType metric,
762
                   double *distortion,
763
                   ExceptionInfo *exception)
764
0
{
765
0
  return GetImageChannelDistortion(reference_image,compare_image,AllChannels,
766
0
                                   metric,distortion,exception);
767
0
}
768

769
/*
770
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
771
%                                                                             %
772
%                                                                             %
773
%                                                                             %
774
%  I s I m a g e s E q u a l                                                  %
775
%                                                                             %
776
%                                                                             %
777
%                                                                             %
778
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
779
%
780
%  IsImagesEqual() measures the difference between colors at each pixel
781
%  location of two images.  A value other than 0 means the colors match
782
%  exactly.  Otherwise an error measure is computed by summing over all
783
%  pixels in an image the distance squared in RGB space between each image
784
%  pixel and its corresponding pixel in the reference image.  The error
785
%  measure is assigned to these image members:
786
%
787
%    o mean_error_per_pixel:  The mean error for any single pixel in
788
%      the image.
789
%
790
%    o normalized_mean_error:  The normalized mean quantization error for
791
%      any single pixel in the image.  This distance measure is normalized to
792
%      a range between 0 and 1.  It is independent of the range of red, green,
793
%      and blue values in the image.
794
%
795
%    o normalized_maximum_error:  The normalized maximum quantization
796
%      error for any single pixel in the image.  This distance measure is
797
%      normalized to a range between 0 and 1.  It is independent of the range
798
%      of red, green, and blue values in your image.
799
%
800
%  A small normalized mean square error, accessed as
801
%  image->normalized_mean_error, suggests the images are very similar in
802
%  spatial layout and color.
803
%
804
%  The format of the IsImagesEqual method is:
805
%
806
%      MagickBool IsImagesEqual(Image *image,const Image *reference)
807
%
808
%  A description of each parameter follows.
809
%
810
%    o image: The image.
811
%
812
%    o reference: The reference image.
813
%
814
*/
815
typedef struct _ErrorStatistics {
816
  double
817
    maximum,
818
    total;
819
} ErrorStatistics;
820
821
static MagickPassFail
822
ComputePixelError(void *mutable_data,
823
                  const void *immutable_data,
824
                  const Image * restrict first_image,
825
                  const PixelPacket * restrict first_pixels,
826
                  const IndexPacket * restrict first_indexes,
827
                  const Image * restrict second_image,
828
                  const PixelPacket * restrict second_pixels,
829
                  const IndexPacket * restrict second_indexes,
830
                  const long npixels,
831
                  ExceptionInfo *exception)
832
0
{
833
0
  ErrorStatistics
834
0
    *stats = (ErrorStatistics *) mutable_data;
835
836
0
  double
837
0
    difference,
838
0
    distance,
839
0
    distance_squared,
840
0
    stats_maximum,
841
0
    stats_total;
842
843
0
  register long
844
0
    i;
845
846
0
  const MagickBool
847
0
    first_image_matte = first_image->matte,
848
0
    second_image_matte = second_image->matte,
849
0
    matte = (first_image->matte || second_image->matte);
850
851
0
  ARG_NOT_USED(immutable_data);
852
0
  ARG_NOT_USED(first_indexes);
853
0
  ARG_NOT_USED(second_image);
854
0
  ARG_NOT_USED(second_indexes);
855
0
  ARG_NOT_USED(exception);
856
857
0
  stats_maximum=0.0;
858
0
  stats_total=0.0;
859
860
0
  for (i=0; i < npixels; i++)
861
0
    {
862
0
      difference=(first_pixels[i].red-(double) second_pixels[i].red)/MaxRGBDouble;
863
0
      distance_squared=(difference*difference);
864
865
0
      difference=(first_pixels[i].green-(double) second_pixels[i].green)/MaxRGBDouble;
866
0
      distance_squared+=(difference*difference);
867
868
0
      difference=(first_pixels[i].blue-(double) second_pixels[i].blue)/MaxRGBDouble;
869
0
      distance_squared+=(difference*difference);
870
871
0
      if (matte)
872
0
        {
873
0
          const Quantum first_opacity = first_image_matte ? first_pixels[i].opacity : OpaqueOpacity;
874
0
          const Quantum second_opacity = second_image_matte ? second_pixels[i].opacity : OpaqueOpacity;
875
0
          difference=(first_opacity-(double) second_opacity)/MaxRGBDouble;
876
0
          distance_squared+=(difference*difference);
877
0
        }
878
0
      distance=sqrt(distance_squared);
879
880
0
      stats_total+=distance;
881
0
      if (distance > stats_maximum)
882
0
        stats_maximum=distance;
883
0
    }
884
885
#if defined(HAVE_OPENMP)
886
#  pragma omp critical (GM_ComputePixelError)
887
#endif
888
0
  {
889
0
    stats->total+=stats_total;
890
891
0
    if (stats_maximum > stats->maximum)
892
0
      stats->maximum=stats_maximum;
893
0
  }
894
0
  return (MagickPass);
895
0
}
896
897
MagickExport MagickBool
898
IsImagesEqual(Image *image,const Image *reference)
899
0
{
900
0
  ErrorStatistics
901
0
    stats;
902
903
0
  double
904
0
    mean_error_per_pixel,
905
0
    normalize,
906
0
    number_pixels;
907
908
0
  MagickBool
909
0
    matte;
910
911
  /*
912
    Initialize measurement.
913
  */
914
0
  assert(image != (const Image *) NULL);
915
0
  assert(image->signature == MagickSignature);
916
0
  assert(reference != (const Image *) NULL);
917
0
  assert(reference->signature == MagickSignature);
918
0
  (void) memset(&image->error,0,sizeof(ErrorInfo));
919
0
  if ((image->rows != reference->rows) ||
920
0
      (image->columns != reference->columns))
921
0
    ThrowBinaryException3(ImageError,UnableToCompareImages,
922
0
      ImageSizeDiffers);
923
0
  if ((image->colorspace != reference->colorspace) &&
924
0
      (!IsRGBColorspace(image->colorspace) || !IsRGBColorspace(reference->colorspace)))
925
0
    ThrowBinaryException3(ImageError,UnableToCompareImages,
926
0
      ImageColorspaceDiffers);
927
928
  /*
929
    For each pixel, collect error statistics.
930
  */
931
0
  matte=(image->matte || reference->matte);
932
0
  number_pixels=(double) image->columns*image->rows;
933
934
0
  stats.maximum=0.0;
935
0
  stats.total=0.0;
936
937
0
  (void) PixelIterateDualRead(ComputePixelError,
938
0
                              NULL,
939
0
                              "[%s]*[%s] Compute pixel error ...",
940
0
                              &stats, NULL,
941
0
                              image->columns,image->rows,
942
0
                              image,0,0,
943
0
                              reference,0,0,
944
0
                              &image->exception);
945
946
  /*
947
    Compute final error statistics.
948
  */
949
950
0
  if (matte)
951
0
    normalize = sqrt(4.0); /* sqrt(1.0*1.0+1.0*1.0+1.0*1.0+1.0*1.0) */
952
0
  else
953
0
    normalize = sqrt(3.0); /* sqrt(1.0*1.0+1.0*1.0+1.0*1.0) */
954
0
  mean_error_per_pixel=stats.total/number_pixels;
955
0
  image->error.mean_error_per_pixel=mean_error_per_pixel*MaxRGBDouble;
956
0
  image->error.normalized_mean_error=mean_error_per_pixel/normalize;
957
0
  image->error.normalized_maximum_error=stats.maximum/normalize;
958
0
  return(image->error.normalized_mean_error == 0.0);
959
0
}
960

961
/*
962
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
963
%                                                                             %
964
%                                                                             %
965
%                                                                             %
966
%   I n i t i a l i z e D i f f e r e n c e I m a g e O p t i o n s           %
967
%                                                                             %
968
%                                                                             %
969
%                                                                             %
970
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
971
%
972
%  InitializeDifferenceImageOptions() assigns default options to a user-provided
973
%  DifferenceImageOptions structure.  This function should always be used
974
%  to initialize the DifferenceImageOptions structure prior to making any
975
%  changes to it.
976
%
977
%  The format of the InitializeDifferenceImageOptions method is:
978
%
979
%      void InitializeDifferenceImageOptions(DifferenceImageOptions *options,
980
%                                            ExceptionInfo *exception)
981
%
982
%  A description of each parameter follows:
983
%
984
%    o options: pointer to DifferenceImageOptions structure to initialize.
985
%
986
%    o exception: Return any errors or warnings in this structure.
987
%
988
*/
989
MagickExport void
990
InitializeDifferenceImageOptions(DifferenceImageOptions *options,
991
                                 ExceptionInfo *exception)
992
0
{
993
0
  assert(options != (DifferenceImageOptions *) NULL);
994
0
  ARG_NOT_USED(exception);
995
996
0
  memset(options,0,sizeof(DifferenceImageOptions));
997
0
  options->channel=AllChannels;
998
0
  options->highlight_style=TintHighlightStyle;
999
  /* (void) QueryColorDatabase(HighlightColor,&options->highlight_color,exception); */
1000
0
  HighlightColorInit(&options->highlight_color);
1001
0
}
1002
1003

1004
/*
1005
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1006
%                                                                             %
1007
%                                                                             %
1008
%                                                                             %
1009
%   I n i t i a l i z e D i f f e r e n c e S t a t i s t i c s               %
1010
%                                                                             %
1011
%                                                                             %
1012
%                                                                             %
1013
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1014
%
1015
%  InitializeDifferenceStatistics() assigns default options to a user-provided
1016
%  DifferenceStatistics structure.
1017
%
1018
%  The format of the InitializeDifferenceStatistics method is:
1019
%
1020
%      void InitializeDifferenceStatistics(DifferenceStatistics *options,
1021
%                                          ExceptionInfo *exception)
1022
%
1023
%  A description of each parameter follows:
1024
%
1025
%    o options: pointer to DifferenceStatistics structure to initialize.
1026
%
1027
%    o exception: Return any errors or warnings in this structure.
1028
%
1029
*/
1030
MagickExport void
1031
InitializeDifferenceStatistics(DifferenceStatistics *statistics,
1032
                               ExceptionInfo *exception)
1033
0
{
1034
0
  ARG_NOT_USED(exception);
1035
0
  assert(statistics != (DifferenceStatistics *) NULL);
1036
0
  statistics->red=0.0;
1037
0
  statistics->green=0.0;
1038
0
  statistics->blue=0.0;
1039
0
  statistics->opacity=0.0;
1040
0
  statistics->combined=0.0;
1041
0
}