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