go-libwebp

Experimental translation from libwebp to Go source.
Log | Files | Refs | README | LICENSE

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 }