blob: a91c8b34e7325d84d9458ccd78b5238ab7378e3a [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
Urvang Joshi47dd0702016-04-04 18:43:58 +000016#include <assert.h>
Pascal Massimino96201e52015-11-06 07:47:03 +010017#include <limits.h>
18#include <stdio.h>
19#include <stdlib.h> // for 'strtod'.
20#include <string.h> // for 'strcmp'.
21
22#include "./anim_util.h"
23
24#if defined(_MSC_VER) && _MSC_VER < 1900
25#define snprintf _snprintf
26#endif
27
28// Returns true if 'a + b' will overflow.
29static int AdditionWillOverflow(int a, int b) {
30 return (b > 0) && (a > INT_MAX - b);
31}
32
Urvang Joshi47dd0702016-04-04 18:43:58 +000033static int FramesAreEqual(const uint8_t* const rgba1,
34 const uint8_t* const rgba2, int width, int height) {
35 const int stride = width * 4; // Always true for 'DecodedFrame.rgba'.
36 return !memcmp(rgba1, rgba2, stride * height);
37}
38
39static WEBP_INLINE int PixelsAreSimilar(uint32_t src, uint32_t dst,
40 int max_allowed_diff) {
41 const int src_a = (src >> 24) & 0xff;
42 const int src_r = (src >> 16) & 0xff;
43 const int src_g = (src >> 8) & 0xff;
44 const int src_b = (src >> 0) & 0xff;
45 const int dst_a = (dst >> 24) & 0xff;
46 const int dst_r = (dst >> 16) & 0xff;
47 const int dst_g = (dst >> 8) & 0xff;
48 const int dst_b = (dst >> 0) & 0xff;
49
50 return (abs(src_r * src_a - dst_r * dst_a) <= (max_allowed_diff * 255)) &&
51 (abs(src_g * src_a - dst_g * dst_a) <= (max_allowed_diff * 255)) &&
52 (abs(src_b * src_a - dst_b * dst_a) <= (max_allowed_diff * 255)) &&
53 (abs(src_a - dst_a) <= max_allowed_diff);
54}
55
56static int FramesAreSimilar(const uint8_t* const rgba1,
57 const uint8_t* const rgba2,
58 int width, int height, int max_allowed_diff) {
59 int i, j;
60 assert(max_allowed_diff > 0);
61 for (j = 0; j < height; ++j) {
62 for (i = 0; i < width; ++i) {
63 const int stride = width * 4;
64 const size_t offset = j * stride + i;
65 if (!PixelsAreSimilar(rgba1[offset], rgba2[offset], max_allowed_diff)) {
66 return 0;
67 }
68 }
69 }
70 return 1;
71}
72
73// Minimize number of frames by combining successive frames that have at max
74// 'max_diff' difference per channel between corresponding pixels.
75static void MinimizeAnimationFrames(AnimatedImage* const img, int max_diff) {
Pascal Massimino96201e52015-11-06 07:47:03 +010076 uint32_t i;
77 for (i = 1; i < img->num_frames; ++i) {
78 DecodedFrame* const frame1 = &img->frames[i - 1];
79 DecodedFrame* const frame2 = &img->frames[i];
80 const uint8_t* const rgba1 = frame1->rgba;
81 const uint8_t* const rgba2 = frame2->rgba;
Urvang Joshi47dd0702016-04-04 18:43:58 +000082 int should_merge_frames = 0;
Pascal Massimino96201e52015-11-06 07:47:03 +010083 // If merging frames will result in integer overflow for 'duration',
84 // skip merging.
85 if (AdditionWillOverflow(frame1->duration, frame2->duration)) continue;
Urvang Joshi47dd0702016-04-04 18:43:58 +000086 if (max_diff > 0) {
87 should_merge_frames = FramesAreSimilar(rgba1, rgba2, img->canvas_width,
88 img->canvas_height, max_diff);
89 } else {
90 should_merge_frames =
91 FramesAreEqual(rgba1, rgba2, img->canvas_width, img->canvas_height);
92 }
93 if (should_merge_frames) { // Merge 'i+1'th frame into 'i'th frame.
Pascal Massimino96201e52015-11-06 07:47:03 +010094 frame1->duration += frame2->duration;
95 if (i + 1 < img->num_frames) {
96 memmove(&img->frames[i], &img->frames[i + 1],
97 (img->num_frames - i - 1) * sizeof(*img->frames));
98 }
99 --img->num_frames;
100 --i;
101 }
102 }
103}
104
105static int CompareValues(uint32_t a, uint32_t b, const char* output_str) {
106 if (a != b) {
107 fprintf(stderr, "%s: %d vs %d\n", output_str, a, b);
108 return 0;
109 }
110 return 1;
111}
112
113// Note: As long as frame durations and reconstructed frames are identical, it
114// is OK for other aspects like offsets, dispose/blend method to vary.
115static int CompareAnimatedImagePair(const AnimatedImage* const img1,
116 const AnimatedImage* const img2,
117 int premultiply,
118 double min_psnr) {
119 int ok = 1;
120 const int is_multi_frame_image = (img1->num_frames > 1);
121 uint32_t i;
122
123 ok = CompareValues(img1->canvas_width, img2->canvas_width,
124 "Canvas width mismatch") && ok;
125 ok = CompareValues(img1->canvas_height, img2->canvas_height,
126 "Canvas height mismatch") && ok;
127 ok = CompareValues(img1->num_frames, img2->num_frames,
128 "Frame count mismatch") && ok;
129 if (!ok) return 0; // These are fatal failures, can't proceed.
130
131 if (is_multi_frame_image) { // Checks relevant for multi-frame images only.
132 ok = CompareValues(img1->loop_count, img2->loop_count,
133 "Loop count mismatch") && ok;
134 ok = CompareValues(img1->bgcolor, img2->bgcolor,
135 "Background color mismatch") && ok;
136 }
137
138 for (i = 0; i < img1->num_frames; ++i) {
139 // Pixel-by-pixel comparison.
140 const uint8_t* const rgba1 = img1->frames[i].rgba;
141 const uint8_t* const rgba2 = img2->frames[i].rgba;
142 int max_diff;
143 double psnr;
144 if (is_multi_frame_image) { // Check relevant for multi-frame images only.
145 const char format[] = "Frame #%d, duration mismatch";
146 char tmp[sizeof(format) + 8];
147 ok = ok && (snprintf(tmp, sizeof(tmp), format, i) >= 0);
148 ok = ok && CompareValues(img1->frames[i].duration,
149 img2->frames[i].duration, tmp);
150 }
151 GetDiffAndPSNR(rgba1, rgba2, img1->canvas_width, img1->canvas_height,
152 premultiply, &max_diff, &psnr);
153 if (min_psnr > 0.) {
154 if (psnr < min_psnr) {
155 fprintf(stderr, "Frame #%d, psnr = %.2lf (min_psnr = %f)\n", i,
156 psnr, min_psnr);
157 ok = 0;
158 }
159 } else {
160 if (max_diff != 0) {
161 fprintf(stderr, "Frame #%d, max pixel diff: %d\n", i, max_diff);
162 ok = 0;
163 }
164 }
165 }
166 return ok;
167}
168
169static void Help(void) {
James Zernb4106c42015-12-16 18:08:01 -0800170 printf("Usage: anim_diff <image1> <image2> [options]\n");
171 printf("\nOptions:\n");
172 printf(" -dump_frames <folder> dump decoded frames in PAM format\n");
173 printf(" -min_psnr <float> ... minimum per-frame PSNR\n");
174 printf(" -raw_comparison ..... if this flag is not used, RGB is\n");
175 printf(" premultiplied before comparison\n");
Urvang Joshi47dd0702016-04-04 18:43:58 +0000176#ifdef WEBP_EXPERIMENTAL_FEATURES
177 printf(" -max_diff <int> ..... maximum allowed difference per channel "
178 " between corresponding pixels in subsequent"
179 " frames\n");
180#endif
Pascal Massimino96201e52015-11-06 07:47:03 +0100181}
182
183int main(int argc, const char* argv[]) {
184 int return_code = -1;
185 int dump_frames = 0;
186 const char* dump_folder = NULL;
187 double min_psnr = 0.;
188 int got_input1 = 0;
189 int got_input2 = 0;
190 int premultiply = 1;
Urvang Joshi47dd0702016-04-04 18:43:58 +0000191 int max_diff = 0;
Pascal Massimino96201e52015-11-06 07:47:03 +0100192 int i, c;
193 const char* files[2] = { NULL, NULL };
194 AnimatedImage images[2];
195
196 if (argc < 3) {
197 Help();
198 return -1;
199 }
200
201 for (c = 1; c < argc; ++c) {
202 int parse_error = 0;
203 if (!strcmp(argv[c], "-dump_frames")) {
204 if (c < argc - 1) {
205 dump_frames = 1;
206 dump_folder = argv[++c];
207 } else {
208 parse_error = 1;
209 }
210 } else if (!strcmp(argv[c], "-min_psnr")) {
211 if (c < argc - 1) {
212 const char* const v = argv[++c];
213 char* end = NULL;
214 const double d = strtod(v, &end);
215 if (end == v) {
216 parse_error = 1;
217 fprintf(stderr, "Error! '%s' is not a floating point number.\n", v);
218 }
219 min_psnr = d;
220 } else {
221 parse_error = 1;
222 }
223 } else if (!strcmp(argv[c], "-raw_comparison")) {
224 premultiply = 0;
Urvang Joshi47dd0702016-04-04 18:43:58 +0000225#ifdef WEBP_EXPERIMENTAL_FEATURES
226 } else if (!strcmp(argv[c], "-max_diff")) {
227 if (c < argc - 1) {
228 const char* const v = argv[++c];
229 char* end = NULL;
230 const int n = (int)strtol(v, &end, 10);
231 if (end == v) {
232 parse_error = 1;
233 fprintf(stderr, "Error! '%s' is not an integer.\n", v);
234 }
235 max_diff = n;
236 } else {
237 parse_error = 1;
238 }
239#endif
Pascal Massimino96201e52015-11-06 07:47:03 +0100240 } else {
241 if (!got_input1) {
242 files[0] = argv[c];
243 got_input1 = 1;
244 } else if (!got_input2) {
245 files[1] = argv[c];
246 got_input2 = 1;
247 } else {
248 parse_error = 1;
249 }
250 }
251 if (parse_error) {
252 Help();
253 return -1;
254 }
255 }
256 if (!got_input2) {
257 Help();
258 return -1;
259 }
260
261 if (dump_frames) {
262 printf("Dumping decoded frames in: %s\n", dump_folder);
263 }
264
265 memset(images, 0, sizeof(images));
266 for (i = 0; i < 2; ++i) {
267 printf("Decoding file: %s\n", files[i]);
268 if (!ReadAnimatedImage(files[i], &images[i], dump_frames, dump_folder)) {
269 fprintf(stderr, "Error decoding file: %s\n Aborting.\n", files[i]);
270 return_code = -2;
271 goto End;
272 } else {
Urvang Joshi47dd0702016-04-04 18:43:58 +0000273 MinimizeAnimationFrames(&images[i], max_diff);
Pascal Massimino96201e52015-11-06 07:47:03 +0100274 }
275 }
276
277 if (!CompareAnimatedImagePair(&images[0], &images[1],
278 premultiply, min_psnr)) {
279 fprintf(stderr, "\nFiles %s and %s differ.\n", files[0], files[1]);
280 return_code = -3;
281 } else {
282 printf("\nFiles %s and %s are identical.\n", files[0], files[1]);
283 return_code = 0;
284 }
285 End:
286 ClearAnimatedImage(&images[0]);
287 ClearAnimatedImage(&images[1]);
288 return return_code;
289}