Blame examples/anim_diff.c

Packit 9c6abc
// Copyright 2015 Google Inc. All Rights Reserved.
Packit 9c6abc
//
Packit 9c6abc
// Use of this source code is governed by a BSD-style license
Packit 9c6abc
// that can be found in the COPYING file in the root of the source
Packit 9c6abc
// tree. An additional intellectual property rights grant can be found
Packit 9c6abc
// in the file PATENTS. All contributing project authors may
Packit 9c6abc
// be found in the AUTHORS file in the root of the source tree.
Packit 9c6abc
// -----------------------------------------------------------------------------
Packit 9c6abc
//
Packit 9c6abc
// Checks if given pair of animated GIF/WebP images are identical:
Packit 9c6abc
// That is: their reconstructed canvases match pixel-by-pixel and their other
Packit 9c6abc
// animation properties (loop count etc) also match.
Packit 9c6abc
//
Packit 9c6abc
// example: anim_diff foo.gif bar.webp
Packit 9c6abc
Packit 9c6abc
#include <assert.h>
Packit 9c6abc
#include <limits.h>
Packit 9c6abc
#include <stdio.h>
Packit 9c6abc
#include <stdlib.h>  // for 'strtod'.
Packit 9c6abc
#include <string.h>  // for 'strcmp'.
Packit 9c6abc
Packit 9c6abc
#include "./anim_util.h"
Packit 9c6abc
Packit 9c6abc
#if defined(_MSC_VER) && _MSC_VER < 1900
Packit 9c6abc
#define snprintf _snprintf
Packit 9c6abc
#endif
Packit 9c6abc
Packit 9c6abc
// Returns true if 'a + b' will overflow.
Packit 9c6abc
static int AdditionWillOverflow(int a, int b) {
Packit 9c6abc
  return (b > 0) && (a > INT_MAX - b);
Packit 9c6abc
}
Packit 9c6abc
Packit 9c6abc
static int FramesAreEqual(const uint8_t* const rgba1,
Packit 9c6abc
                          const uint8_t* const rgba2, int width, int height) {
Packit 9c6abc
  const int stride = width * 4;  // Always true for 'DecodedFrame.rgba'.
Packit 9c6abc
  return !memcmp(rgba1, rgba2, stride * height);
Packit 9c6abc
}
Packit 9c6abc
Packit 9c6abc
static WEBP_INLINE int PixelsAreSimilar(uint32_t src, uint32_t dst,
Packit 9c6abc
                                        int max_allowed_diff) {
Packit 9c6abc
  const int src_a = (src >> 24) & 0xff;
Packit 9c6abc
  const int src_r = (src >> 16) & 0xff;
Packit 9c6abc
  const int src_g = (src >> 8) & 0xff;
Packit 9c6abc
  const int src_b = (src >> 0) & 0xff;
Packit 9c6abc
  const int dst_a = (dst >> 24) & 0xff;
Packit 9c6abc
  const int dst_r = (dst >> 16) & 0xff;
Packit 9c6abc
  const int dst_g = (dst >> 8) & 0xff;
Packit 9c6abc
  const int dst_b = (dst >> 0) & 0xff;
Packit 9c6abc
Packit 9c6abc
  return (abs(src_r * src_a - dst_r * dst_a) <= (max_allowed_diff * 255)) &&
Packit 9c6abc
         (abs(src_g * src_a - dst_g * dst_a) <= (max_allowed_diff * 255)) &&
Packit 9c6abc
         (abs(src_b * src_a - dst_b * dst_a) <= (max_allowed_diff * 255)) &&
Packit 9c6abc
         (abs(src_a - dst_a) <= max_allowed_diff);
Packit 9c6abc
}
Packit 9c6abc
Packit 9c6abc
static int FramesAreSimilar(const uint8_t* const rgba1,
Packit 9c6abc
                            const uint8_t* const rgba2,
Packit 9c6abc
                            int width, int height, int max_allowed_diff) {
Packit 9c6abc
  int i, j;
Packit 9c6abc
  assert(max_allowed_diff > 0);
Packit 9c6abc
  for (j = 0; j < height; ++j) {
Packit 9c6abc
    for (i = 0; i < width; ++i) {
Packit 9c6abc
      const int stride = width * 4;
Packit 9c6abc
      const size_t offset = j * stride + i;
Packit 9c6abc
      if (!PixelsAreSimilar(rgba1[offset], rgba2[offset], max_allowed_diff)) {
Packit 9c6abc
        return 0;
Packit 9c6abc
      }
Packit 9c6abc
    }
Packit 9c6abc
  }
Packit 9c6abc
  return 1;
Packit 9c6abc
}
Packit 9c6abc
Packit 9c6abc
// Minimize number of frames by combining successive frames that have at max
Packit 9c6abc
// 'max_diff' difference per channel between corresponding pixels.
Packit 9c6abc
static void MinimizeAnimationFrames(AnimatedImage* const img, int max_diff) {
Packit 9c6abc
  uint32_t i;
Packit 9c6abc
  for (i = 1; i < img->num_frames; ++i) {
Packit 9c6abc
    DecodedFrame* const frame1 = &img->frames[i - 1];
Packit 9c6abc
    DecodedFrame* const frame2 = &img->frames[i];
Packit 9c6abc
    const uint8_t* const rgba1 = frame1->rgba;
Packit 9c6abc
    const uint8_t* const rgba2 = frame2->rgba;
Packit 9c6abc
    int should_merge_frames = 0;
Packit 9c6abc
    // If merging frames will result in integer overflow for 'duration',
Packit 9c6abc
    // skip merging.
Packit 9c6abc
    if (AdditionWillOverflow(frame1->duration, frame2->duration)) continue;
Packit 9c6abc
    if (max_diff > 0) {
Packit 9c6abc
      should_merge_frames = FramesAreSimilar(rgba1, rgba2, img->canvas_width,
Packit 9c6abc
                                             img->canvas_height, max_diff);
Packit 9c6abc
    } else {
Packit 9c6abc
      should_merge_frames =
Packit 9c6abc
          FramesAreEqual(rgba1, rgba2, img->canvas_width, img->canvas_height);
Packit 9c6abc
    }
Packit 9c6abc
    if (should_merge_frames) {  // Merge 'i+1'th frame into 'i'th frame.
Packit 9c6abc
      frame1->duration += frame2->duration;
Packit 9c6abc
      if (i + 1 < img->num_frames) {
Packit 9c6abc
        memmove(&img->frames[i], &img->frames[i + 1],
Packit 9c6abc
                (img->num_frames - i - 1) * sizeof(*img->frames));
Packit 9c6abc
      }
Packit 9c6abc
      --img->num_frames;
Packit 9c6abc
      --i;
Packit 9c6abc
    }
Packit 9c6abc
  }
Packit 9c6abc
}
Packit 9c6abc
Packit 9c6abc
static int CompareValues(uint32_t a, uint32_t b, const char* output_str) {
Packit 9c6abc
  if (a != b) {
Packit 9c6abc
    fprintf(stderr, "%s: %d vs %d\n", output_str, a, b);
Packit 9c6abc
    return 0;
Packit 9c6abc
  }
Packit 9c6abc
  return 1;
Packit 9c6abc
}
Packit 9c6abc
Packit 9c6abc
static int CompareBackgroundColor(uint32_t bg1, uint32_t bg2, int premultiply) {
Packit 9c6abc
  if (premultiply) {
Packit 9c6abc
    const int alpha1 = (bg1 >> 24) & 0xff;
Packit 9c6abc
    const int alpha2 = (bg2 >> 24) & 0xff;
Packit 9c6abc
    if (alpha1 == 0 && alpha2 == 0) return 1;
Packit 9c6abc
  }
Packit 9c6abc
  if (bg1 != bg2) {
Packit 9c6abc
    fprintf(stderr, "Background color mismatch: 0x%08x vs 0x%08x\n",
Packit 9c6abc
            bg1, bg2);
Packit 9c6abc
    return 0;
Packit 9c6abc
  }
Packit 9c6abc
  return 1;
Packit 9c6abc
}
Packit 9c6abc
Packit 9c6abc
// Note: As long as frame durations and reconstructed frames are identical, it
Packit 9c6abc
// is OK for other aspects like offsets, dispose/blend method to vary.
Packit 9c6abc
static int CompareAnimatedImagePair(const AnimatedImage* const img1,
Packit 9c6abc
                                    const AnimatedImage* const img2,
Packit 9c6abc
                                    int premultiply,
Packit 9c6abc
                                    double min_psnr) {
Packit 9c6abc
  int ok = 1;
Packit 9c6abc
  const int is_multi_frame_image = (img1->num_frames > 1);
Packit 9c6abc
  uint32_t i;
Packit 9c6abc
Packit 9c6abc
  ok = CompareValues(img1->canvas_width, img2->canvas_width,
Packit 9c6abc
                     "Canvas width mismatch") && ok;
Packit 9c6abc
  ok = CompareValues(img1->canvas_height, img2->canvas_height,
Packit 9c6abc
                     "Canvas height mismatch") && ok;
Packit 9c6abc
  ok = CompareValues(img1->num_frames, img2->num_frames,
Packit 9c6abc
                     "Frame count mismatch") && ok;
Packit 9c6abc
  if (!ok) return 0;  // These are fatal failures, can't proceed.
Packit 9c6abc
Packit 9c6abc
  if (is_multi_frame_image) {  // Checks relevant for multi-frame images only.
Packit 9c6abc
    ok = CompareValues(img1->loop_count, img2->loop_count,
Packit 9c6abc
                       "Loop count mismatch") && ok;
Packit 9c6abc
    ok = CompareBackgroundColor(img1->bgcolor, img2->bgcolor,
Packit 9c6abc
                                premultiply) && ok;
Packit 9c6abc
  }
Packit 9c6abc
Packit 9c6abc
  for (i = 0; i < img1->num_frames; ++i) {
Packit 9c6abc
    // Pixel-by-pixel comparison.
Packit 9c6abc
    const uint8_t* const rgba1 = img1->frames[i].rgba;
Packit 9c6abc
    const uint8_t* const rgba2 = img2->frames[i].rgba;
Packit 9c6abc
    int max_diff;
Packit 9c6abc
    double psnr;
Packit 9c6abc
    if (is_multi_frame_image) {  // Check relevant for multi-frame images only.
Packit 9c6abc
      const char format[] = "Frame #%d, duration mismatch";
Packit 9c6abc
      char tmp[sizeof(format) + 8];
Packit 9c6abc
      ok = ok && (snprintf(tmp, sizeof(tmp), format, i) >= 0);
Packit 9c6abc
      ok = ok && CompareValues(img1->frames[i].duration,
Packit 9c6abc
                               img2->frames[i].duration, tmp);
Packit 9c6abc
    }
Packit 9c6abc
    GetDiffAndPSNR(rgba1, rgba2, img1->canvas_width, img1->canvas_height,
Packit 9c6abc
                   premultiply, &max_diff, &psnr);
Packit 9c6abc
    if (min_psnr > 0.) {
Packit 9c6abc
      if (psnr < min_psnr) {
Packit 9c6abc
        fprintf(stderr, "Frame #%d, psnr = %.2lf (min_psnr = %f)\n", i,
Packit 9c6abc
                psnr, min_psnr);
Packit 9c6abc
        ok = 0;
Packit 9c6abc
      }
Packit 9c6abc
    } else {
Packit 9c6abc
      if (max_diff != 0) {
Packit 9c6abc
        fprintf(stderr, "Frame #%d, max pixel diff: %d\n", i, max_diff);
Packit 9c6abc
        ok = 0;
Packit 9c6abc
      }
Packit 9c6abc
    }
Packit 9c6abc
  }
Packit 9c6abc
  return ok;
Packit 9c6abc
}
Packit 9c6abc
Packit 9c6abc
static void Help(void) {
Packit 9c6abc
  printf("Usage: anim_diff <image1> <image2> [options]\n");
Packit 9c6abc
  printf("\nOptions:\n");
Packit 9c6abc
  printf("  -dump_frames <folder> dump decoded frames in PAM format\n");
Packit 9c6abc
  printf("  -min_psnr <float> ... minimum per-frame PSNR\n");
Packit 9c6abc
  printf("  -raw_comparison ..... if this flag is not used, RGB is\n");
Packit 9c6abc
  printf("                        premultiplied before comparison\n");
Packit 9c6abc
  printf("  -max_diff <int> ..... maximum allowed difference per channel\n"
Packit 9c6abc
         "                        between corresponding pixels in subsequent\n"
Packit 9c6abc
         "                        frames\n");
Packit 9c6abc
  printf("  -h .................. this help\n");
Packit 9c6abc
  printf("  -version ............ print version number and exit\n");
Packit 9c6abc
}
Packit 9c6abc
Packit 9c6abc
int main(int argc, const char* argv[]) {
Packit 9c6abc
  int return_code = -1;
Packit 9c6abc
  int dump_frames = 0;
Packit 9c6abc
  const char* dump_folder = NULL;
Packit 9c6abc
  double min_psnr = 0.;
Packit 9c6abc
  int got_input1 = 0;
Packit 9c6abc
  int got_input2 = 0;
Packit 9c6abc
  int premultiply = 1;
Packit 9c6abc
  int max_diff = 0;
Packit 9c6abc
  int i, c;
Packit 9c6abc
  const char* files[2] = { NULL, NULL };
Packit 9c6abc
  AnimatedImage images[2];
Packit 9c6abc
Packit 9c6abc
  for (c = 1; c < argc; ++c) {
Packit 9c6abc
    int parse_error = 0;
Packit 9c6abc
    if (!strcmp(argv[c], "-dump_frames")) {
Packit 9c6abc
      if (c < argc - 1) {
Packit 9c6abc
        dump_frames = 1;
Packit 9c6abc
        dump_folder = argv[++c];
Packit 9c6abc
      } else {
Packit 9c6abc
        parse_error = 1;
Packit 9c6abc
      }
Packit 9c6abc
    } else if (!strcmp(argv[c], "-min_psnr")) {
Packit 9c6abc
      if (c < argc - 1) {
Packit 9c6abc
        const char* const v = argv[++c];
Packit 9c6abc
        char* end = NULL;
Packit 9c6abc
        const double d = strtod(v, &end;;
Packit 9c6abc
        if (end == v) {
Packit 9c6abc
          parse_error = 1;
Packit 9c6abc
          fprintf(stderr, "Error! '%s' is not a floating point number.\n", v);
Packit 9c6abc
        }
Packit 9c6abc
        min_psnr = d;
Packit 9c6abc
      } else {
Packit 9c6abc
        parse_error = 1;
Packit 9c6abc
      }
Packit 9c6abc
    } else if (!strcmp(argv[c], "-raw_comparison")) {
Packit 9c6abc
      premultiply = 0;
Packit 9c6abc
    } else if (!strcmp(argv[c], "-max_diff")) {
Packit 9c6abc
      if (c < argc - 1) {
Packit 9c6abc
        const char* const v = argv[++c];
Packit 9c6abc
        char* end = NULL;
Packit 9c6abc
        const int n = (int)strtol(v, &end, 10);
Packit 9c6abc
        if (end == v) {
Packit 9c6abc
          parse_error = 1;
Packit 9c6abc
          fprintf(stderr, "Error! '%s' is not an integer.\n", v);
Packit 9c6abc
        }
Packit 9c6abc
        max_diff = n;
Packit 9c6abc
      } else {
Packit 9c6abc
        parse_error = 1;
Packit 9c6abc
      }
Packit 9c6abc
    } else if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) {
Packit 9c6abc
      Help();
Packit 9c6abc
      return 0;
Packit 9c6abc
    } else if (!strcmp(argv[c], "-version")) {
Packit 9c6abc
      int dec_version, demux_version;
Packit 9c6abc
      GetAnimatedImageVersions(&dec_version, &demux_version);
Packit 9c6abc
      printf("WebP Decoder version: %d.%d.%d\nWebP Demux version: %d.%d.%d\n",
Packit 9c6abc
             (dec_version >> 16) & 0xff, (dec_version >> 8) & 0xff,
Packit 9c6abc
             (dec_version >> 0) & 0xff,
Packit 9c6abc
             (demux_version >> 16) & 0xff, (demux_version >> 8) & 0xff,
Packit 9c6abc
             (demux_version >> 0) & 0xff);
Packit 9c6abc
      return 0;
Packit 9c6abc
    } else {
Packit 9c6abc
      if (!got_input1) {
Packit 9c6abc
        files[0] = argv[c];
Packit 9c6abc
        got_input1 = 1;
Packit 9c6abc
      } else if (!got_input2) {
Packit 9c6abc
        files[1] = argv[c];
Packit 9c6abc
        got_input2 = 1;
Packit 9c6abc
      } else {
Packit 9c6abc
        parse_error = 1;
Packit 9c6abc
      }
Packit 9c6abc
    }
Packit 9c6abc
    if (parse_error) {
Packit 9c6abc
      Help();
Packit 9c6abc
      return -1;
Packit 9c6abc
    }
Packit 9c6abc
  }
Packit 9c6abc
  if (argc < 3) {
Packit 9c6abc
    Help();
Packit 9c6abc
    return -1;
Packit 9c6abc
  }
Packit 9c6abc
Packit 9c6abc
Packit 9c6abc
  if (!got_input2) {
Packit 9c6abc
    Help();
Packit 9c6abc
    return -1;
Packit 9c6abc
  }
Packit 9c6abc
Packit 9c6abc
  if (dump_frames) {
Packit 9c6abc
    printf("Dumping decoded frames in: %s\n", dump_folder);
Packit 9c6abc
  }
Packit 9c6abc
Packit 9c6abc
  memset(images, 0, sizeof(images));
Packit 9c6abc
  for (i = 0; i < 2; ++i) {
Packit 9c6abc
    printf("Decoding file: %s\n", files[i]);
Packit 9c6abc
    if (!ReadAnimatedImage(files[i], &images[i], dump_frames, dump_folder)) {
Packit 9c6abc
      fprintf(stderr, "Error decoding file: %s\n Aborting.\n", files[i]);
Packit 9c6abc
      return_code = -2;
Packit 9c6abc
      goto End;
Packit 9c6abc
    } else {
Packit 9c6abc
      MinimizeAnimationFrames(&images[i], max_diff);
Packit 9c6abc
    }
Packit 9c6abc
  }
Packit 9c6abc
Packit 9c6abc
  if (!CompareAnimatedImagePair(&images[0], &images[1],
Packit 9c6abc
                                premultiply, min_psnr)) {
Packit 9c6abc
    fprintf(stderr, "\nFiles %s and %s differ.\n", files[0], files[1]);
Packit 9c6abc
    return_code = -3;
Packit 9c6abc
  } else {
Packit 9c6abc
    printf("\nFiles %s and %s are identical.\n", files[0], files[1]);
Packit 9c6abc
    return_code = 0;
Packit 9c6abc
  }
Packit 9c6abc
 End:
Packit 9c6abc
  ClearAnimatedImage(&images[0]);
Packit 9c6abc
  ClearAnimatedImage(&images[1]);
Packit 9c6abc
  return return_code;
Packit 9c6abc
}