blob: df24a1a9b5a9fa38f58b3c4c017102e61ac39ef3 [file] [log] [blame]
Pascal Massimino96201e52015-11-06 07:47:03 +01001// Copyright 2015 Google Inc. All Rights Reserved.
2//
3// Use of this source code is governed by a BSD-style license
4// that can be found in the COPYING file in the root of the source
5// tree. An additional intellectual property rights grant can be found
6// in the file PATENTS. All contributing project authors may
7// be found in the AUTHORS file in the root of the source tree.
8// -----------------------------------------------------------------------------
9//
10// Checks if given pair of animated GIF/WebP images are identical:
11// That is: their reconstructed canvases match pixel-by-pixel and their other
12// animation properties (loop count etc) also match.
13//
14// example: anim_diff foo.gif bar.webp
15
16#include <limits.h>
17#include <stdio.h>
18#include <stdlib.h> // for 'strtod'.
19#include <string.h> // for 'strcmp'.
20
21#include "./anim_util.h"
22
23#if defined(_MSC_VER) && _MSC_VER < 1900
24#define snprintf _snprintf
25#endif
26
27// Returns true if 'a + b' will overflow.
28static int AdditionWillOverflow(int a, int b) {
29 return (b > 0) && (a > INT_MAX - b);
30}
31
32// Minimize number of frames by combining successive frames that have exact same
33// ARGB data into a single longer duration frame.
34static void MinimizeAnimationFrames(AnimatedImage* const img) {
35 uint32_t i;
36 for (i = 1; i < img->num_frames; ++i) {
37 DecodedFrame* const frame1 = &img->frames[i - 1];
38 DecodedFrame* const frame2 = &img->frames[i];
39 const uint8_t* const rgba1 = frame1->rgba;
40 const uint8_t* const rgba2 = frame2->rgba;
41 // If merging frames will result in integer overflow for 'duration',
42 // skip merging.
43 if (AdditionWillOverflow(frame1->duration, frame2->duration)) continue;
44 if (!memcmp(rgba1, rgba2, img->canvas_width * 4 * img->canvas_height)) {
45 // Merge 'i+1'th frame into 'i'th frame.
46 frame1->duration += frame2->duration;
47 if (i + 1 < img->num_frames) {
48 memmove(&img->frames[i], &img->frames[i + 1],
49 (img->num_frames - i - 1) * sizeof(*img->frames));
50 }
51 --img->num_frames;
52 --i;
53 }
54 }
55}
56
57static int CompareValues(uint32_t a, uint32_t b, const char* output_str) {
58 if (a != b) {
59 fprintf(stderr, "%s: %d vs %d\n", output_str, a, b);
60 return 0;
61 }
62 return 1;
63}
64
65// Note: As long as frame durations and reconstructed frames are identical, it
66// is OK for other aspects like offsets, dispose/blend method to vary.
67static int CompareAnimatedImagePair(const AnimatedImage* const img1,
68 const AnimatedImage* const img2,
69 int premultiply,
70 double min_psnr) {
71 int ok = 1;
72 const int is_multi_frame_image = (img1->num_frames > 1);
73 uint32_t i;
74
75 ok = CompareValues(img1->canvas_width, img2->canvas_width,
76 "Canvas width mismatch") && ok;
77 ok = CompareValues(img1->canvas_height, img2->canvas_height,
78 "Canvas height mismatch") && ok;
79 ok = CompareValues(img1->num_frames, img2->num_frames,
80 "Frame count mismatch") && ok;
81 if (!ok) return 0; // These are fatal failures, can't proceed.
82
83 if (is_multi_frame_image) { // Checks relevant for multi-frame images only.
84 ok = CompareValues(img1->loop_count, img2->loop_count,
85 "Loop count mismatch") && ok;
86 ok = CompareValues(img1->bgcolor, img2->bgcolor,
87 "Background color mismatch") && ok;
88 }
89
90 for (i = 0; i < img1->num_frames; ++i) {
91 // Pixel-by-pixel comparison.
92 const uint8_t* const rgba1 = img1->frames[i].rgba;
93 const uint8_t* const rgba2 = img2->frames[i].rgba;
94 int max_diff;
95 double psnr;
96 if (is_multi_frame_image) { // Check relevant for multi-frame images only.
97 const char format[] = "Frame #%d, duration mismatch";
98 char tmp[sizeof(format) + 8];
99 ok = ok && (snprintf(tmp, sizeof(tmp), format, i) >= 0);
100 ok = ok && CompareValues(img1->frames[i].duration,
101 img2->frames[i].duration, tmp);
102 }
103 GetDiffAndPSNR(rgba1, rgba2, img1->canvas_width, img1->canvas_height,
104 premultiply, &max_diff, &psnr);
105 if (min_psnr > 0.) {
106 if (psnr < min_psnr) {
107 fprintf(stderr, "Frame #%d, psnr = %.2lf (min_psnr = %f)\n", i,
108 psnr, min_psnr);
109 ok = 0;
110 }
111 } else {
112 if (max_diff != 0) {
113 fprintf(stderr, "Frame #%d, max pixel diff: %d\n", i, max_diff);
114 ok = 0;
115 }
116 }
117 }
118 return ok;
119}
120
121static void Help(void) {
James Zernb4106c42015-12-16 18:08:01 -0800122 printf("Usage: anim_diff <image1> <image2> [options]\n");
123 printf("\nOptions:\n");
124 printf(" -dump_frames <folder> dump decoded frames in PAM format\n");
125 printf(" -min_psnr <float> ... minimum per-frame PSNR\n");
126 printf(" -raw_comparison ..... if this flag is not used, RGB is\n");
127 printf(" premultiplied before comparison\n");
Pascal Massimino96201e52015-11-06 07:47:03 +0100128}
129
130int main(int argc, const char* argv[]) {
131 int return_code = -1;
132 int dump_frames = 0;
133 const char* dump_folder = NULL;
134 double min_psnr = 0.;
135 int got_input1 = 0;
136 int got_input2 = 0;
137 int premultiply = 1;
138 int i, c;
139 const char* files[2] = { NULL, NULL };
140 AnimatedImage images[2];
141
142 if (argc < 3) {
143 Help();
144 return -1;
145 }
146
147 for (c = 1; c < argc; ++c) {
148 int parse_error = 0;
149 if (!strcmp(argv[c], "-dump_frames")) {
150 if (c < argc - 1) {
151 dump_frames = 1;
152 dump_folder = argv[++c];
153 } else {
154 parse_error = 1;
155 }
156 } else if (!strcmp(argv[c], "-min_psnr")) {
157 if (c < argc - 1) {
158 const char* const v = argv[++c];
159 char* end = NULL;
160 const double d = strtod(v, &end);
161 if (end == v) {
162 parse_error = 1;
163 fprintf(stderr, "Error! '%s' is not a floating point number.\n", v);
164 }
165 min_psnr = d;
166 } else {
167 parse_error = 1;
168 }
169 } else if (!strcmp(argv[c], "-raw_comparison")) {
170 premultiply = 0;
171 } else {
172 if (!got_input1) {
173 files[0] = argv[c];
174 got_input1 = 1;
175 } else if (!got_input2) {
176 files[1] = argv[c];
177 got_input2 = 1;
178 } else {
179 parse_error = 1;
180 }
181 }
182 if (parse_error) {
183 Help();
184 return -1;
185 }
186 }
187 if (!got_input2) {
188 Help();
189 return -1;
190 }
191
192 if (dump_frames) {
193 printf("Dumping decoded frames in: %s\n", dump_folder);
194 }
195
196 memset(images, 0, sizeof(images));
197 for (i = 0; i < 2; ++i) {
198 printf("Decoding file: %s\n", files[i]);
199 if (!ReadAnimatedImage(files[i], &images[i], dump_frames, dump_folder)) {
200 fprintf(stderr, "Error decoding file: %s\n Aborting.\n", files[i]);
201 return_code = -2;
202 goto End;
203 } else {
204 MinimizeAnimationFrames(&images[i]);
205 }
206 }
207
208 if (!CompareAnimatedImagePair(&images[0], &images[1],
209 premultiply, min_psnr)) {
210 fprintf(stderr, "\nFiles %s and %s differ.\n", files[0], files[1]);
211 return_code = -3;
212 } else {
213 printf("\nFiles %s and %s are identical.\n", files[0], files[1]);
214 return_code = 0;
215 }
216 End:
217 ClearAnimatedImage(&images[0]);
218 ClearAnimatedImage(&images[1]);
219 return return_code;
220}