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