xref: /aosp_15_r20/external/webp/examples/anim_diff.c (revision b2055c353e87c8814eb2b6b1b11112a1562253bd)
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