migrate anim_diff tool from C++ to C89
+ jenkins fixes for native config (library order)
+ add a missing -lm
+ replace log10 by log, just in case
+ partially reverted configure.ac to remove the C++ part
Change-Id: Iee099c544451b23c6cfaca53d5a95d2d332e066e
diff --git a/examples/anim_diff.c b/examples/anim_diff.c
new file mode 100644
index 0000000..dab9c6b
--- /dev/null
+++ b/examples/anim_diff.c
@@ -0,0 +1,216 @@
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// Checks if given pair of animated GIF/WebP images are identical:
+// That is: their reconstructed canvases match pixel-by-pixel and their other
+// animation properties (loop count etc) also match.
+//
+// example: anim_diff foo.gif bar.webp
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h> // for 'strtod'.
+#include <string.h> // for 'strcmp'.
+
+#include "./anim_util.h"
+
+#if defined(_MSC_VER) && _MSC_VER < 1900
+#define snprintf _snprintf
+#endif
+
+// Returns true if 'a + b' will overflow.
+static int AdditionWillOverflow(int a, int b) {
+ return (b > 0) && (a > INT_MAX - b);
+}
+
+// Minimize number of frames by combining successive frames that have exact same
+// ARGB data into a single longer duration frame.
+static void MinimizeAnimationFrames(AnimatedImage* const img) {
+ uint32_t i;
+ for (i = 1; i < img->num_frames; ++i) {
+ DecodedFrame* const frame1 = &img->frames[i - 1];
+ DecodedFrame* const frame2 = &img->frames[i];
+ const uint8_t* const rgba1 = frame1->rgba;
+ const uint8_t* const rgba2 = frame2->rgba;
+ // If merging frames will result in integer overflow for 'duration',
+ // skip merging.
+ if (AdditionWillOverflow(frame1->duration, frame2->duration)) continue;
+ if (!memcmp(rgba1, rgba2, img->canvas_width * 4 * img->canvas_height)) {
+ // Merge 'i+1'th frame into 'i'th frame.
+ frame1->duration += frame2->duration;
+ if (i + 1 < img->num_frames) {
+ memmove(&img->frames[i], &img->frames[i + 1],
+ (img->num_frames - i - 1) * sizeof(*img->frames));
+ }
+ --img->num_frames;
+ --i;
+ }
+ }
+}
+
+static int CompareValues(uint32_t a, uint32_t b, const char* output_str) {
+ if (a != b) {
+ fprintf(stderr, "%s: %d vs %d\n", output_str, a, b);
+ return 0;
+ }
+ return 1;
+}
+
+// Note: As long as frame durations and reconstructed frames are identical, it
+// is OK for other aspects like offsets, dispose/blend method to vary.
+static int CompareAnimatedImagePair(const AnimatedImage* const img1,
+ const AnimatedImage* const img2,
+ int premultiply,
+ double min_psnr) {
+ int ok = 1;
+ const int is_multi_frame_image = (img1->num_frames > 1);
+ uint32_t i;
+
+ ok = CompareValues(img1->canvas_width, img2->canvas_width,
+ "Canvas width mismatch") && ok;
+ ok = CompareValues(img1->canvas_height, img2->canvas_height,
+ "Canvas height mismatch") && ok;
+ ok = CompareValues(img1->num_frames, img2->num_frames,
+ "Frame count mismatch") && ok;
+ if (!ok) return 0; // These are fatal failures, can't proceed.
+
+ if (is_multi_frame_image) { // Checks relevant for multi-frame images only.
+ ok = CompareValues(img1->loop_count, img2->loop_count,
+ "Loop count mismatch") && ok;
+ ok = CompareValues(img1->bgcolor, img2->bgcolor,
+ "Background color mismatch") && ok;
+ }
+
+ for (i = 0; i < img1->num_frames; ++i) {
+ // Pixel-by-pixel comparison.
+ const uint8_t* const rgba1 = img1->frames[i].rgba;
+ const uint8_t* const rgba2 = img2->frames[i].rgba;
+ int max_diff;
+ double psnr;
+ if (is_multi_frame_image) { // Check relevant for multi-frame images only.
+ const char format[] = "Frame #%d, duration mismatch";
+ char tmp[sizeof(format) + 8];
+ ok = ok && (snprintf(tmp, sizeof(tmp), format, i) >= 0);
+ ok = ok && CompareValues(img1->frames[i].duration,
+ img2->frames[i].duration, tmp);
+ }
+ GetDiffAndPSNR(rgba1, rgba2, img1->canvas_width, img1->canvas_height,
+ premultiply, &max_diff, &psnr);
+ if (min_psnr > 0.) {
+ if (psnr < min_psnr) {
+ fprintf(stderr, "Frame #%d, psnr = %.2lf (min_psnr = %f)\n", i,
+ psnr, min_psnr);
+ ok = 0;
+ }
+ } else {
+ if (max_diff != 0) {
+ fprintf(stderr, "Frame #%d, max pixel diff: %d\n", i, max_diff);
+ ok = 0;
+ }
+ }
+ }
+ return ok;
+}
+
+static void Help(void) {
+ printf("\nUsage: anim_diff <image1> <image2> [-dump_frames <folder>] "
+ "[-min_psnr <float>][-raw_comparison]\n");
+}
+
+int main(int argc, const char* argv[]) {
+ int return_code = -1;
+ int dump_frames = 0;
+ const char* dump_folder = NULL;
+ double min_psnr = 0.;
+ int got_input1 = 0;
+ int got_input2 = 0;
+ int premultiply = 1;
+ int i, c;
+ const char* files[2] = { NULL, NULL };
+ AnimatedImage images[2];
+
+ if (argc < 3) {
+ Help();
+ return -1;
+ }
+
+ for (c = 1; c < argc; ++c) {
+ int parse_error = 0;
+ if (!strcmp(argv[c], "-dump_frames")) {
+ if (c < argc - 1) {
+ dump_frames = 1;
+ dump_folder = argv[++c];
+ } else {
+ parse_error = 1;
+ }
+ } else if (!strcmp(argv[c], "-min_psnr")) {
+ if (c < argc - 1) {
+ const char* const v = argv[++c];
+ char* end = NULL;
+ const double d = strtod(v, &end);
+ if (end == v) {
+ parse_error = 1;
+ fprintf(stderr, "Error! '%s' is not a floating point number.\n", v);
+ }
+ min_psnr = d;
+ } else {
+ parse_error = 1;
+ }
+ } else if (!strcmp(argv[c], "-raw_comparison")) {
+ premultiply = 0;
+ } else {
+ if (!got_input1) {
+ files[0] = argv[c];
+ got_input1 = 1;
+ } else if (!got_input2) {
+ files[1] = argv[c];
+ got_input2 = 1;
+ } else {
+ parse_error = 1;
+ }
+ }
+ if (parse_error) {
+ Help();
+ return -1;
+ }
+ }
+ if (!got_input2) {
+ Help();
+ return -1;
+ }
+
+ if (dump_frames) {
+ printf("Dumping decoded frames in: %s\n", dump_folder);
+ }
+
+ memset(images, 0, sizeof(images));
+ for (i = 0; i < 2; ++i) {
+ printf("Decoding file: %s\n", files[i]);
+ if (!ReadAnimatedImage(files[i], &images[i], dump_frames, dump_folder)) {
+ fprintf(stderr, "Error decoding file: %s\n Aborting.\n", files[i]);
+ return_code = -2;
+ goto End;
+ } else {
+ MinimizeAnimationFrames(&images[i]);
+ }
+ }
+
+ if (!CompareAnimatedImagePair(&images[0], &images[1],
+ premultiply, min_psnr)) {
+ fprintf(stderr, "\nFiles %s and %s differ.\n", files[0], files[1]);
+ return_code = -3;
+ } else {
+ printf("\nFiles %s and %s are identical.\n", files[0], files[1]);
+ return_code = 0;
+ }
+ End:
+ ClearAnimatedImage(&images[0]);
+ ClearAnimatedImage(&images[1]);
+ return return_code;
+}