1 // 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 <assert.h>
17 #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 #include "./example_util.h"
24 #include "./unicode.h"
25
26 #if defined(_MSC_VER) && _MSC_VER < 1900
27 #define snprintf _snprintf
28 #endif
29
30 // Returns true if 'a + b' will overflow.
AdditionWillOverflow(int a,int b)31 static int AdditionWillOverflow(int a, int b) {
32 return (b > 0) && (a > INT_MAX - b);
33 }
34
FramesAreEqual(const uint8_t * const rgba1,const uint8_t * const rgba2,int width,int height)35 static int FramesAreEqual(const uint8_t* const rgba1,
36 const uint8_t* const rgba2, int width, int height) {
37 const int stride = width * 4; // Always true for 'DecodedFrame.rgba'.
38 return !memcmp(rgba1, rgba2, stride * height);
39 }
40
PixelsAreSimilar(uint32_t src,uint32_t dst,int max_allowed_diff)41 static WEBP_INLINE int PixelsAreSimilar(uint32_t src, uint32_t dst,
42 int max_allowed_diff) {
43 const int src_a = (src >> 24) & 0xff;
44 const int src_r = (src >> 16) & 0xff;
45 const int src_g = (src >> 8) & 0xff;
46 const int src_b = (src >> 0) & 0xff;
47 const int dst_a = (dst >> 24) & 0xff;
48 const int dst_r = (dst >> 16) & 0xff;
49 const int dst_g = (dst >> 8) & 0xff;
50 const int dst_b = (dst >> 0) & 0xff;
51
52 return (abs(src_r * src_a - dst_r * dst_a) <= (max_allowed_diff * 255)) &&
53 (abs(src_g * src_a - dst_g * dst_a) <= (max_allowed_diff * 255)) &&
54 (abs(src_b * src_a - dst_b * dst_a) <= (max_allowed_diff * 255)) &&
55 (abs(src_a - dst_a) <= max_allowed_diff);
56 }
57
FramesAreSimilar(const uint8_t * const rgba1,const uint8_t * const rgba2,int width,int height,int max_allowed_diff)58 static int FramesAreSimilar(const uint8_t* const rgba1,
59 const uint8_t* const rgba2,
60 int width, int height, int max_allowed_diff) {
61 int i, j;
62 assert(max_allowed_diff > 0);
63 for (j = 0; j < height; ++j) {
64 for (i = 0; i < width; ++i) {
65 const int stride = width * 4;
66 const size_t offset = j * stride + i;
67 if (!PixelsAreSimilar(rgba1[offset], rgba2[offset], max_allowed_diff)) {
68 return 0;
69 }
70 }
71 }
72 return 1;
73 }
74
75 // Minimize number of frames by combining successive frames that have at max
76 // 'max_diff' difference per channel between corresponding pixels.
MinimizeAnimationFrames(AnimatedImage * const img,int max_diff)77 static void MinimizeAnimationFrames(AnimatedImage* const img, int max_diff) {
78 uint32_t i;
79 for (i = 1; i < img->num_frames; ++i) {
80 DecodedFrame* const frame1 = &img->frames[i - 1];
81 DecodedFrame* const frame2 = &img->frames[i];
82 const uint8_t* const rgba1 = frame1->rgba;
83 const uint8_t* const rgba2 = frame2->rgba;
84 int should_merge_frames = 0;
85 // If merging frames will result in integer overflow for 'duration',
86 // skip merging.
87 if (AdditionWillOverflow(frame1->duration, frame2->duration)) continue;
88 if (max_diff > 0) {
89 should_merge_frames = FramesAreSimilar(rgba1, rgba2, img->canvas_width,
90 img->canvas_height, max_diff);
91 } else {
92 should_merge_frames =
93 FramesAreEqual(rgba1, rgba2, img->canvas_width, img->canvas_height);
94 }
95 if (should_merge_frames) { // Merge 'i+1'th frame into 'i'th frame.
96 frame1->duration += frame2->duration;
97 if (i + 1 < img->num_frames) {
98 memmove(&img->frames[i], &img->frames[i + 1],
99 (img->num_frames - i - 1) * sizeof(*img->frames));
100 }
101 --img->num_frames;
102 --i;
103 }
104 }
105 }
106
CompareValues(uint32_t a,uint32_t b,const char * output_str)107 static int CompareValues(uint32_t a, uint32_t b, const char* output_str) {
108 if (a != b) {
109 fprintf(stderr, "%s: %d vs %d\n", output_str, a, b);
110 return 0;
111 }
112 return 1;
113 }
114
CompareBackgroundColor(uint32_t bg1,uint32_t bg2,int premultiply)115 static int CompareBackgroundColor(uint32_t bg1, uint32_t bg2, int premultiply) {
116 if (premultiply) {
117 const int alpha1 = (bg1 >> 24) & 0xff;
118 const int alpha2 = (bg2 >> 24) & 0xff;
119 if (alpha1 == 0 && alpha2 == 0) return 1;
120 }
121 if (bg1 != bg2) {
122 fprintf(stderr, "Background color mismatch: 0x%08x vs 0x%08x\n",
123 bg1, bg2);
124 return 0;
125 }
126 return 1;
127 }
128
129 // Note: As long as frame durations and reconstructed frames are identical, it
130 // is OK for other aspects like offsets, dispose/blend method to vary.
CompareAnimatedImagePair(const AnimatedImage * const img1,const AnimatedImage * const img2,int premultiply,double min_psnr)131 static int CompareAnimatedImagePair(const AnimatedImage* const img1,
132 const AnimatedImage* const img2,
133 int premultiply,
134 double min_psnr) {
135 int ok = 1;
136 const int is_multi_frame_image = (img1->num_frames > 1);
137 uint32_t i;
138
139 ok = CompareValues(img1->canvas_width, img2->canvas_width,
140 "Canvas width mismatch") && ok;
141 ok = CompareValues(img1->canvas_height, img2->canvas_height,
142 "Canvas height mismatch") && ok;
143 ok = CompareValues(img1->num_frames, img2->num_frames,
144 "Frame count mismatch") && ok;
145 if (!ok) return 0; // These are fatal failures, can't proceed.
146
147 if (is_multi_frame_image) { // Checks relevant for multi-frame images only.
148 int max_loop_count_workaround = 0;
149 // Transcodes to webp increase the gif loop count by 1 for compatibility.
150 // When the gif has the maximum value the webp value will be off by one.
151 if ((img1->format == ANIM_GIF && img1->loop_count == 65536 &&
152 img2->format == ANIM_WEBP && img2->loop_count == 65535) ||
153 (img1->format == ANIM_WEBP && img1->loop_count == 65535 &&
154 img2->format == ANIM_GIF && img2->loop_count == 65536)) {
155 max_loop_count_workaround = 1;
156 }
157 ok = (max_loop_count_workaround ||
158 CompareValues(img1->loop_count, img2->loop_count,
159 "Loop count mismatch")) && ok;
160 ok = CompareBackgroundColor(img1->bgcolor, img2->bgcolor,
161 premultiply) && ok;
162 }
163
164 for (i = 0; i < img1->num_frames; ++i) {
165 // Pixel-by-pixel comparison.
166 const uint8_t* const rgba1 = img1->frames[i].rgba;
167 const uint8_t* const rgba2 = img2->frames[i].rgba;
168 int max_diff;
169 double psnr;
170 if (is_multi_frame_image) { // Check relevant for multi-frame images only.
171 const char format[] = "Frame #%d, duration mismatch";
172 char tmp[sizeof(format) + 8];
173 ok = ok && (snprintf(tmp, sizeof(tmp), format, i) >= 0);
174 ok = ok && CompareValues(img1->frames[i].duration,
175 img2->frames[i].duration, tmp);
176 }
177 GetDiffAndPSNR(rgba1, rgba2, img1->canvas_width, img1->canvas_height,
178 premultiply, &max_diff, &psnr);
179 if (min_psnr > 0.) {
180 if (psnr < min_psnr) {
181 fprintf(stderr, "Frame #%d, psnr = %.2lf (min_psnr = %f)\n", i,
182 psnr, min_psnr);
183 ok = 0;
184 }
185 } else {
186 if (max_diff != 0) {
187 fprintf(stderr, "Frame #%d, max pixel diff: %d\n", i, max_diff);
188 ok = 0;
189 }
190 }
191 }
192 return ok;
193 }
194
Help(void)195 static void Help(void) {
196 printf("Usage: anim_diff <image1> <image2> [options]\n");
197 printf("\nOptions:\n");
198 printf(" -dump_frames <folder> dump decoded frames in PAM format\n");
199 printf(" -min_psnr <float> ... minimum per-frame PSNR\n");
200 printf(" -raw_comparison ..... if this flag is not used, RGB is\n");
201 printf(" premultiplied before comparison\n");
202 printf(" -max_diff <int> ..... maximum allowed difference per channel\n"
203 " between corresponding pixels in subsequent\n"
204 " frames\n");
205 printf(" -h .................. this help\n");
206 printf(" -version ............ print version number and exit\n");
207 }
208
main(int argc,const char * argv[])209 int main(int argc, const char* argv[]) {
210 int return_code = -1;
211 int dump_frames = 0;
212 const char* dump_folder = NULL;
213 double min_psnr = 0.;
214 int got_input1 = 0;
215 int got_input2 = 0;
216 int premultiply = 1;
217 int max_diff = 0;
218 int i, c;
219 const char* files[2] = { NULL, NULL };
220 AnimatedImage images[2];
221
222 INIT_WARGV(argc, argv);
223
224 for (c = 1; c < argc; ++c) {
225 int parse_error = 0;
226 if (!strcmp(argv[c], "-dump_frames")) {
227 if (c < argc - 1) {
228 dump_frames = 1;
229 dump_folder = (const char*)GET_WARGV(argv, ++c);
230 } else {
231 parse_error = 1;
232 }
233 } else if (!strcmp(argv[c], "-min_psnr")) {
234 if (c < argc - 1) {
235 min_psnr = ExUtilGetFloat(argv[++c], &parse_error);
236 } else {
237 parse_error = 1;
238 }
239 } else if (!strcmp(argv[c], "-raw_comparison")) {
240 premultiply = 0;
241 } else if (!strcmp(argv[c], "-max_diff")) {
242 if (c < argc - 1) {
243 max_diff = ExUtilGetInt(argv[++c], 0, &parse_error);
244 } else {
245 parse_error = 1;
246 }
247 } else if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) {
248 Help();
249 FREE_WARGV_AND_RETURN(0);
250 } else if (!strcmp(argv[c], "-version")) {
251 int dec_version, demux_version;
252 GetAnimatedImageVersions(&dec_version, &demux_version);
253 printf("WebP Decoder version: %d.%d.%d\nWebP Demux version: %d.%d.%d\n",
254 (dec_version >> 16) & 0xff, (dec_version >> 8) & 0xff,
255 (dec_version >> 0) & 0xff,
256 (demux_version >> 16) & 0xff, (demux_version >> 8) & 0xff,
257 (demux_version >> 0) & 0xff);
258 FREE_WARGV_AND_RETURN(0);
259 } else {
260 if (!got_input1) {
261 files[0] = (const char*)GET_WARGV(argv, c);
262 got_input1 = 1;
263 } else if (!got_input2) {
264 files[1] = (const char*)GET_WARGV(argv, c);
265 got_input2 = 1;
266 } else {
267 parse_error = 1;
268 }
269 }
270 if (parse_error) {
271 Help();
272 FREE_WARGV_AND_RETURN(-1);
273 }
274 }
275 if (argc < 3) {
276 Help();
277 FREE_WARGV_AND_RETURN(-1);
278 }
279
280
281 if (!got_input2) {
282 Help();
283 FREE_WARGV_AND_RETURN(-1);
284 }
285
286 if (dump_frames) {
287 WPRINTF("Dumping decoded frames in: %s\n", (const W_CHAR*)dump_folder);
288 }
289
290 memset(images, 0, sizeof(images));
291 for (i = 0; i < 2; ++i) {
292 WPRINTF("Decoding file: %s\n", (const W_CHAR*)files[i]);
293 if (!ReadAnimatedImage(files[i], &images[i], dump_frames, dump_folder)) {
294 WFPRINTF(stderr, "Error decoding file: %s\n Aborting.\n",
295 (const W_CHAR*)files[i]);
296 return_code = -2;
297 goto End;
298 } else {
299 MinimizeAnimationFrames(&images[i], max_diff);
300 }
301 }
302
303 if (!CompareAnimatedImagePair(&images[0], &images[1],
304 premultiply, min_psnr)) {
305 WFPRINTF(stderr, "\nFiles %s and %s differ.\n", (const W_CHAR*)files[0],
306 (const W_CHAR*)files[1]);
307 return_code = -3;
308 } else {
309 WPRINTF("\nFiles %s and %s are identical.\n", (const W_CHAR*)files[0],
310 (const W_CHAR*)files[1]);
311 return_code = 0;
312 }
313 End:
314 ClearAnimatedImage(&images[0]);
315 ClearAnimatedImage(&images[1]);
316 FREE_WARGV_AND_RETURN(return_code);
317 }
318