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