go-libwebp

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

webpinfo.c (40834B)


      1 // Copyright 2017 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 //  Command-line tool to print out the chunk level structure of WebP files
     11 //  along with basic integrity checks.
     12 //
     13 //  Author: Hui Su (huisu@google.com)
     14 
     15 #include <assert.h>
     16 #include <stdio.h>
     17 #include <stdlib.h>
     18 #include <string.h>
     19 
     20 #ifdef HAVE_CONFIG_H
     21 #include "webp/config.h"
     22 #endif
     23 
     24 #include "../imageio/imageio_util.h"
     25 #include "./unicode.h"
     26 #include "webp/decode.h"
     27 #include "webp/format_constants.h"
     28 #include "webp/mux_types.h"
     29 #include "webp/types.h"
     30 
     31 #if defined(_MSC_VER) && _MSC_VER < 1900
     32 #define snprintf _snprintf
     33 #endif
     34 
     35 #define LOG_ERROR(MESSAGE)                     \
     36   do {                                         \
     37     if (webp_info->show_diagnosis) {           \
     38       fprintf(stderr, "Error: %s\n", MESSAGE); \
     39     }                                          \
     40   } while (0)
     41 
     42 #define LOG_WARN(MESSAGE)                        \
     43   do {                                           \
     44     if (webp_info->show_diagnosis) {             \
     45       fprintf(stderr, "Warning: %s\n", MESSAGE); \
     46     }                                            \
     47     ++webp_info->num_warnings;                   \
     48   } while (0)
     49 
     50 static const char* const kFormats[3] = {
     51   "Unknown",
     52   "Lossy",
     53   "Lossless"
     54 };
     55 
     56 static const char* const kLosslessTransforms[4] = {
     57   "Predictor",
     58   "Cross Color",
     59   "Subtract Green",
     60   "Color Indexing"
     61 };
     62 
     63 static const char* const kAlphaFilterMethods[4] = {
     64   "None",
     65   "Horizontal",
     66   "Vertical",
     67   "Gradient"
     68 };
     69 
     70 typedef enum {
     71   WEBP_INFO_OK = 0,
     72   WEBP_INFO_TRUNCATED_DATA,
     73   WEBP_INFO_PARSE_ERROR,
     74   WEBP_INFO_INVALID_PARAM,
     75   WEBP_INFO_BITSTREAM_ERROR,
     76   WEBP_INFO_MISSING_DATA,
     77   WEBP_INFO_INVALID_COMMAND
     78 } WebPInfoStatus;
     79 
     80 typedef enum ChunkID {
     81   CHUNK_VP8,
     82   CHUNK_VP8L,
     83   CHUNK_VP8X,
     84   CHUNK_ALPHA,
     85   CHUNK_ANIM,
     86   CHUNK_ANMF,
     87   CHUNK_ICCP,
     88   CHUNK_EXIF,
     89   CHUNK_XMP,
     90   CHUNK_UNKNOWN,
     91   CHUNK_TYPES = CHUNK_UNKNOWN
     92 } ChunkID;
     93 
     94 typedef struct {
     95   size_t start;
     96   size_t end;
     97   const uint8_t* buf;
     98 } MemBuffer;
     99 
    100 typedef struct {
    101   size_t offset;
    102   size_t size;
    103   const uint8_t* payload;
    104   ChunkID id;
    105 } ChunkData;
    106 
    107 typedef struct WebPInfo {
    108   int canvas_width;
    109   int canvas_height;
    110   int loop_count;
    111   int num_frames;
    112   int chunk_counts[CHUNK_TYPES];
    113   int anmf_subchunk_counts[3];  // 0 VP8; 1 VP8L; 2 ALPH.
    114   uint32_t bgcolor;
    115   int feature_flags;
    116   int has_alpha;
    117   // Used for parsing ANMF chunks.
    118   int frame_width, frame_height;
    119   size_t anim_frame_data_size;
    120   int is_processing_anim_frame, seen_alpha_subchunk, seen_image_subchunk;
    121   // Print output control.
    122   int quiet, show_diagnosis, show_summary;
    123   int num_warnings;
    124   int parse_bitstream;
    125 } WebPInfo;
    126 
    127 static void WebPInfoInit(WebPInfo* const webp_info) {
    128   memset(webp_info, 0, sizeof(*webp_info));
    129 }
    130 
    131 static const uint32_t kWebPChunkTags[CHUNK_TYPES] = {
    132   MKFOURCC('V', 'P', '8', ' '),
    133   MKFOURCC('V', 'P', '8', 'L'),
    134   MKFOURCC('V', 'P', '8', 'X'),
    135   MKFOURCC('A', 'L', 'P', 'H'),
    136   MKFOURCC('A', 'N', 'I', 'M'),
    137   MKFOURCC('A', 'N', 'M', 'F'),
    138   MKFOURCC('I', 'C', 'C', 'P'),
    139   MKFOURCC('E', 'X', 'I', 'F'),
    140   MKFOURCC('X', 'M', 'P', ' '),
    141 };
    142 
    143 // -----------------------------------------------------------------------------
    144 // Data reading.
    145 
    146 static int GetLE16(const uint8_t* const data) {
    147   return (data[0] << 0) | (data[1] << 8);
    148 }
    149 
    150 static int GetLE24(const uint8_t* const data) {
    151   return GetLE16(data) | (data[2] << 16);
    152 }
    153 
    154 static uint32_t GetLE32(const uint8_t* const data) {
    155   return GetLE16(data) | ((uint32_t)GetLE16(data + 2) << 16);
    156 }
    157 
    158 static int ReadLE16(const uint8_t** data) {
    159   const int val = GetLE16(*data);
    160   *data += 2;
    161   return val;
    162 }
    163 
    164 static int ReadLE24(const uint8_t** data) {
    165   const int val = GetLE24(*data);
    166   *data += 3;
    167   return val;
    168 }
    169 
    170 static uint32_t ReadLE32(const uint8_t** data) {
    171   const uint32_t val = GetLE32(*data);
    172   *data += 4;
    173   return val;
    174 }
    175 
    176 static int ReadFileToWebPData(const char* const filename,
    177                               WebPData* const webp_data) {
    178   const uint8_t* data;
    179   size_t size;
    180   if (!ImgIoUtilReadFile(filename, &data, &size)) return 0;
    181   webp_data->bytes = data;
    182   webp_data->size = size;
    183   return 1;
    184 }
    185 
    186 // -----------------------------------------------------------------------------
    187 // MemBuffer object.
    188 
    189 static void InitMemBuffer(MemBuffer* const mem, const WebPData* webp_data) {
    190   mem->buf = webp_data->bytes;
    191   mem->start = 0;
    192   mem->end = webp_data->size;
    193 }
    194 
    195 static size_t MemDataSize(const MemBuffer* const mem) {
    196   return (mem->end - mem->start);
    197 }
    198 
    199 static const uint8_t* GetBuffer(MemBuffer* const mem) {
    200   return mem->buf + mem->start;
    201 }
    202 
    203 static void Skip(MemBuffer* const mem, size_t size) {
    204   mem->start += size;
    205 }
    206 
    207 static uint32_t ReadMemBufLE32(MemBuffer* const mem) {
    208   const uint8_t* const data = mem->buf + mem->start;
    209   const uint32_t val = GetLE32(data);
    210   assert(MemDataSize(mem) >= 4);
    211   Skip(mem, 4);
    212   return val;
    213 }
    214 
    215 // -----------------------------------------------------------------------------
    216 // Lossy bitstream analysis.
    217 
    218 static int GetBits(const uint8_t* const data, size_t data_size, size_t nb,
    219                    int* val, uint64_t* const bit_pos) {
    220   *val = 0;
    221   while (nb-- > 0) {
    222     const uint64_t p = (*bit_pos)++;
    223     if ((p >> 3) >= data_size) {
    224       return 0;
    225     } else {
    226       const int bit = !!(data[p >> 3] & (128 >> ((p & 7))));
    227       *val = (*val << 1) | bit;
    228     }
    229   }
    230   return 1;
    231 }
    232 
    233 static int GetSignedBits(const uint8_t* const data, size_t data_size, size_t nb,
    234                          int* val, uint64_t* const bit_pos) {
    235   int sign;
    236   if (!GetBits(data, data_size, nb, val, bit_pos)) return 0;
    237   if (!GetBits(data, data_size, 1, &sign, bit_pos)) return 0;
    238   if (sign) *val = -(*val);
    239   return 1;
    240 }
    241 
    242 #define GET_BITS(v, n)                                 \
    243   do {                                                 \
    244     if (!GetBits(data, data_size, n, &(v), bit_pos)) { \
    245       LOG_ERROR("Truncated lossy bitstream.");         \
    246       return WEBP_INFO_TRUNCATED_DATA;                 \
    247     }                                                  \
    248   } while (0)
    249 
    250 #define GET_SIGNED_BITS(v, n)                                \
    251   do {                                                       \
    252     if (!GetSignedBits(data, data_size, n, &(v), bit_pos)) { \
    253       LOG_ERROR("Truncated lossy bitstream.");               \
    254       return WEBP_INFO_TRUNCATED_DATA;                       \
    255     }                                                        \
    256   } while (0)
    257 
    258 static WebPInfoStatus ParseLossySegmentHeader(const WebPInfo* const webp_info,
    259                                               const uint8_t* const data,
    260                                               size_t data_size,
    261                                               uint64_t* const bit_pos) {
    262   int use_segment;
    263   GET_BITS(use_segment, 1);
    264   printf("  Use segment:      %d\n", use_segment);
    265   if (use_segment) {
    266     int update_map, update_data;
    267     GET_BITS(update_map, 1);
    268     GET_BITS(update_data, 1);
    269     printf("  Update map:       %d\n"
    270            "  Update data:      %d\n",
    271            update_map, update_data);
    272     if (update_data) {
    273       int i, a_delta;
    274       int quantizer[4] = {0, 0, 0, 0};
    275       int filter_strength[4] = {0, 0, 0, 0};
    276       GET_BITS(a_delta, 1);
    277       printf("  Absolute delta:   %d\n", a_delta);
    278       for (i = 0; i < 4; ++i) {
    279         int bit;
    280         GET_BITS(bit, 1);
    281         if (bit) GET_SIGNED_BITS(quantizer[i], 7);
    282       }
    283       for (i = 0; i < 4; ++i) {
    284         int bit;
    285         GET_BITS(bit, 1);
    286         if (bit) GET_SIGNED_BITS(filter_strength[i], 6);
    287       }
    288       printf("  Quantizer:        %d %d %d %d\n", quantizer[0], quantizer[1],
    289              quantizer[2], quantizer[3]);
    290       printf("  Filter strength:  %d %d %d %d\n", filter_strength[0],
    291              filter_strength[1], filter_strength[2], filter_strength[3]);
    292     }
    293     if (update_map) {
    294       int i;
    295       int prob_segment[3] = {255, 255, 255};
    296       for (i = 0; i < 3; ++i) {
    297         int bit;
    298         GET_BITS(bit, 1);
    299         if (bit) GET_BITS(prob_segment[i], 8);
    300       }
    301       printf("  Prob segment:     %d %d %d\n",
    302              prob_segment[0], prob_segment[1], prob_segment[2]);
    303     }
    304   }
    305   return WEBP_INFO_OK;
    306 }
    307 
    308 static WebPInfoStatus ParseLossyFilterHeader(const WebPInfo* const webp_info,
    309                                              const uint8_t* const data,
    310                                              size_t data_size,
    311                                              uint64_t* const bit_pos) {
    312   int simple_filter, level, sharpness, use_lf_delta;
    313   GET_BITS(simple_filter, 1);
    314   GET_BITS(level, 6);
    315   GET_BITS(sharpness, 3);
    316   GET_BITS(use_lf_delta, 1);
    317   printf("  Simple filter:    %d\n", simple_filter);
    318   printf("  Level:            %d\n", level);
    319   printf("  Sharpness:        %d\n", sharpness);
    320   printf("  Use lf delta:     %d\n", use_lf_delta);
    321   if (use_lf_delta) {
    322     int update;
    323     GET_BITS(update, 1);
    324     printf("  Update lf delta:  %d\n", update);
    325     if (update) {
    326       int i;
    327       for (i = 0; i < 4 + 4; ++i) {
    328         int temp;
    329         GET_BITS(temp, 1);
    330         if (temp) GET_BITS(temp, 7);
    331       }
    332     }
    333   }
    334   return WEBP_INFO_OK;
    335 }
    336 
    337 static WebPInfoStatus ParseLossyHeader(const ChunkData* const chunk_data,
    338                                        const WebPInfo* const webp_info) {
    339   const uint8_t* data = chunk_data->payload;
    340   size_t data_size = chunk_data->size - CHUNK_HEADER_SIZE;
    341   const uint32_t bits = (uint32_t)data[0] | (data[1] << 8) | (data[2] << 16);
    342   const int key_frame = !(bits & 1);
    343   const int profile = (bits >> 1) & 7;
    344   const int display = (bits >> 4) & 1;
    345   const uint32_t partition0_length = (bits >> 5);
    346   WebPInfoStatus status = WEBP_INFO_OK;
    347   uint64_t bit_position = 0;
    348   uint64_t* const bit_pos = &bit_position;
    349   int colorspace, clamp_type;
    350   printf("  Parsing lossy bitstream...\n");
    351   // Calling WebPGetFeatures() in ProcessImageChunk() should ensure this.
    352   assert(chunk_data->size >= CHUNK_HEADER_SIZE + 10);
    353   if (profile > 3) {
    354     LOG_ERROR("Unknown profile.");
    355     return WEBP_INFO_BITSTREAM_ERROR;
    356   }
    357   if (!display) {
    358     LOG_ERROR("Frame is not displayable.");
    359     return WEBP_INFO_BITSTREAM_ERROR;
    360   }
    361   data += 3;
    362   data_size -= 3;
    363   printf(
    364       "  Key frame:        %s\n"
    365       "  Profile:          %d\n"
    366       "  Display:          Yes\n"
    367       "  Part. 0 length:   %d\n",
    368       key_frame ? "Yes" : "No", profile, partition0_length);
    369   if (key_frame) {
    370     if (!(data[0] == 0x9d && data[1] == 0x01 && data[2] == 0x2a)) {
    371       LOG_ERROR("Invalid lossy bitstream signature.");
    372       return WEBP_INFO_BITSTREAM_ERROR;
    373     }
    374     printf("  Width:            %d\n"
    375            "  X scale:          %d\n"
    376            "  Height:           %d\n"
    377            "  Y scale:          %d\n",
    378            ((data[4] << 8) | data[3]) & 0x3fff, data[4] >> 6,
    379            ((data[6] << 8) | data[5]) & 0x3fff, data[6] >> 6);
    380     data += 7;
    381     data_size -= 7;
    382   } else {
    383     LOG_ERROR("Non-keyframe detected in lossy bitstream.");
    384     return WEBP_INFO_BITSTREAM_ERROR;
    385   }
    386   if (partition0_length >= data_size) {
    387     LOG_ERROR("Bad partition length.");
    388     return WEBP_INFO_BITSTREAM_ERROR;
    389   }
    390   GET_BITS(colorspace, 1);
    391   GET_BITS(clamp_type, 1);
    392   printf("  Color space:      %d\n", colorspace);
    393   printf("  Clamp type:       %d\n", clamp_type);
    394   status = ParseLossySegmentHeader(webp_info, data, data_size, bit_pos);
    395   if (status != WEBP_INFO_OK) return status;
    396   status = ParseLossyFilterHeader(webp_info, data, data_size, bit_pos);
    397   if (status != WEBP_INFO_OK) return status;
    398   {  // Partition number and size.
    399     const uint8_t* part_size = data + partition0_length;
    400     int num_parts, i;
    401     size_t part_data_size;
    402     GET_BITS(num_parts, 2);
    403     num_parts = 1 << num_parts;
    404     if ((int)(data_size - partition0_length) < (num_parts - 1) * 3) {
    405       LOG_ERROR("Truncated lossy bitstream.");
    406       return WEBP_INFO_TRUNCATED_DATA;
    407     }
    408     part_data_size = data_size - partition0_length - (num_parts - 1) * 3;
    409     printf("  Total partitions: %d\n", num_parts);
    410     for (i = 1; i < num_parts; ++i) {
    411       const size_t psize =
    412           part_size[0] | (part_size[1] << 8) | (part_size[2] << 16);
    413       if (psize > part_data_size) {
    414         LOG_ERROR("Truncated partition.");
    415         return WEBP_INFO_TRUNCATED_DATA;
    416       }
    417       printf("  Part. %d length:   %d\n", i, (int)psize);
    418       part_data_size -= psize;
    419       part_size += 3;
    420     }
    421   }
    422   // Quantizer.
    423   {
    424     int base_q, bit;
    425     int dq_y1_dc = 0, dq_y2_dc = 0, dq_y2_ac = 0, dq_uv_dc = 0, dq_uv_ac = 0;
    426     GET_BITS(base_q, 7);
    427     GET_BITS(bit, 1);
    428     if (bit) GET_SIGNED_BITS(dq_y1_dc, 4);
    429     GET_BITS(bit, 1);
    430     if (bit) GET_SIGNED_BITS(dq_y2_dc, 4);
    431     GET_BITS(bit, 1);
    432     if (bit) GET_SIGNED_BITS(dq_y2_ac, 4);
    433     GET_BITS(bit, 1);
    434     if (bit) GET_SIGNED_BITS(dq_uv_dc, 4);
    435     GET_BITS(bit, 1);
    436     if (bit) GET_SIGNED_BITS(dq_uv_ac, 4);
    437     printf("  Base Q:           %d\n", base_q);
    438     printf("  DQ Y1 DC:         %d\n", dq_y1_dc);
    439     printf("  DQ Y2 DC:         %d\n", dq_y2_dc);
    440     printf("  DQ Y2 AC:         %d\n", dq_y2_ac);
    441     printf("  DQ UV DC:         %d\n", dq_uv_dc);
    442     printf("  DQ UV AC:         %d\n", dq_uv_ac);
    443   }
    444   if ((*bit_pos >> 3) >= partition0_length) {
    445     LOG_ERROR("Truncated lossy bitstream.");
    446     return WEBP_INFO_TRUNCATED_DATA;
    447   }
    448   return WEBP_INFO_OK;
    449 }
    450 
    451 // -----------------------------------------------------------------------------
    452 // Lossless bitstream analysis.
    453 
    454 static int LLGetBits(const uint8_t* const data, size_t data_size, size_t nb,
    455                      int* val, uint64_t* const bit_pos) {
    456   uint32_t i = 0;
    457   *val = 0;
    458   while (i < nb) {
    459     const uint64_t p = (*bit_pos)++;
    460     if ((p >> 3) >= data_size) {
    461       return 0;
    462     } else {
    463       const int bit = !!(data[p >> 3] & (1 << ((p & 7))));
    464       *val = *val | (bit << i);
    465       ++i;
    466     }
    467   }
    468   return 1;
    469 }
    470 
    471 #define LL_GET_BITS(v, n)                                \
    472   do {                                                   \
    473     if (!LLGetBits(data, data_size, n, &(v), bit_pos)) { \
    474       LOG_ERROR("Truncated lossless bitstream.");        \
    475       return WEBP_INFO_TRUNCATED_DATA;                   \
    476     }                                                    \
    477   } while (0)
    478 
    479 static WebPInfoStatus ParseLosslessTransform(WebPInfo* const webp_info,
    480                                              const uint8_t* const data,
    481                                              size_t data_size,
    482                                              uint64_t* const  bit_pos) {
    483   int use_transform, block_size, n_colors;
    484   LL_GET_BITS(use_transform, 1);
    485   printf("  Use transform:    %s\n", use_transform ? "Yes" : "No");
    486   if (use_transform) {
    487     int type;
    488     LL_GET_BITS(type, 2);
    489     printf("  1st transform:    %s (%d)\n", kLosslessTransforms[type], type);
    490     switch (type) {
    491       case PREDICTOR_TRANSFORM:
    492       case CROSS_COLOR_TRANSFORM:
    493         LL_GET_BITS(block_size, 3);
    494         block_size = 1 << (block_size + 2);
    495         printf("  Tran. block size: %d\n", block_size);
    496         break;
    497       case COLOR_INDEXING_TRANSFORM:
    498         LL_GET_BITS(n_colors, 8);
    499         n_colors += 1;
    500         printf("  No. of colors:    %d\n", n_colors);
    501         break;
    502       default: break;
    503     }
    504   }
    505   return WEBP_INFO_OK;
    506 }
    507 
    508 static WebPInfoStatus ParseLosslessHeader(const ChunkData* const chunk_data,
    509                                           WebPInfo* const webp_info) {
    510   const uint8_t* data = chunk_data->payload;
    511   size_t data_size = chunk_data->size - CHUNK_HEADER_SIZE;
    512   uint64_t bit_position = 0;
    513   uint64_t* const bit_pos = &bit_position;
    514   WebPInfoStatus status;
    515   printf("  Parsing lossless bitstream...\n");
    516   if (data_size < VP8L_FRAME_HEADER_SIZE) {
    517     LOG_ERROR("Truncated lossless bitstream.");
    518     return WEBP_INFO_TRUNCATED_DATA;
    519   }
    520   if (data[0] != VP8L_MAGIC_BYTE) {
    521     LOG_ERROR("Invalid lossless bitstream signature.");
    522     return WEBP_INFO_BITSTREAM_ERROR;
    523   }
    524   data += 1;
    525   data_size -= 1;
    526   {
    527     int width, height, has_alpha, version;
    528     LL_GET_BITS(width, 14);
    529     LL_GET_BITS(height, 14);
    530     LL_GET_BITS(has_alpha, 1);
    531     LL_GET_BITS(version, 3);
    532     width += 1;
    533     height += 1;
    534     printf("  Width:            %d\n", width);
    535     printf("  Height:           %d\n", height);
    536     printf("  Alpha:            %d\n", has_alpha);
    537     printf("  Version:          %d\n", version);
    538   }
    539   status = ParseLosslessTransform(webp_info, data, data_size, bit_pos);
    540   if (status != WEBP_INFO_OK) return status;
    541   return WEBP_INFO_OK;
    542 }
    543 
    544 static WebPInfoStatus ParseAlphaHeader(const ChunkData* const chunk_data,
    545                                        WebPInfo* const webp_info) {
    546   const uint8_t* data = chunk_data->payload;
    547   size_t data_size = chunk_data->size - CHUNK_HEADER_SIZE;
    548   if (data_size <= ALPHA_HEADER_LEN) {
    549     LOG_ERROR("Truncated ALPH chunk.");
    550     return WEBP_INFO_TRUNCATED_DATA;
    551   }
    552   printf("  Parsing ALPH chunk...\n");
    553   {
    554     const int compression_method = (data[0] >> 0) & 0x03;
    555     const int filter = (data[0] >> 2) & 0x03;
    556     const int pre_processing = (data[0] >> 4) & 0x03;
    557     const int reserved_bits = (data[0] >> 6) & 0x03;
    558     printf("  Compression:      %d\n", compression_method);
    559     printf("  Filter:           %s (%d)\n",
    560            kAlphaFilterMethods[filter], filter);
    561     printf("  Pre-processing:   %d\n", pre_processing);
    562     if (compression_method > ALPHA_LOSSLESS_COMPRESSION) {
    563       LOG_ERROR("Invalid Alpha compression method.");
    564       return WEBP_INFO_BITSTREAM_ERROR;
    565     }
    566     if (pre_processing > ALPHA_PREPROCESSED_LEVELS) {
    567       LOG_ERROR("Invalid Alpha pre-processing method.");
    568       return WEBP_INFO_BITSTREAM_ERROR;
    569     }
    570     if (reserved_bits != 0) {
    571       LOG_WARN("Reserved bits in ALPH chunk header are not all 0.");
    572     }
    573     data += ALPHA_HEADER_LEN;
    574     data_size -= ALPHA_HEADER_LEN;
    575     if (compression_method == ALPHA_LOSSLESS_COMPRESSION) {
    576       uint64_t bit_pos = 0;
    577       WebPInfoStatus status =
    578           ParseLosslessTransform(webp_info, data, data_size, &bit_pos);
    579       if (status != WEBP_INFO_OK) return status;
    580     }
    581   }
    582   return WEBP_INFO_OK;
    583 }
    584 
    585 // -----------------------------------------------------------------------------
    586 // Chunk parsing.
    587 
    588 static WebPInfoStatus ParseRIFFHeader(WebPInfo* const webp_info,
    589                                       MemBuffer* const mem) {
    590   const size_t min_size = RIFF_HEADER_SIZE + CHUNK_HEADER_SIZE;
    591   size_t riff_size;
    592 
    593   if (MemDataSize(mem) < min_size) {
    594     LOG_ERROR("Truncated data detected when parsing RIFF header.");
    595     return WEBP_INFO_TRUNCATED_DATA;
    596   }
    597   if (memcmp(GetBuffer(mem), "RIFF", CHUNK_SIZE_BYTES) ||
    598       memcmp(GetBuffer(mem) + CHUNK_HEADER_SIZE, "WEBP", CHUNK_SIZE_BYTES)) {
    599     LOG_ERROR("Corrupted RIFF header.");
    600     return WEBP_INFO_PARSE_ERROR;
    601   }
    602   riff_size = GetLE32(GetBuffer(mem) + TAG_SIZE);
    603   if (riff_size < CHUNK_HEADER_SIZE) {
    604     LOG_ERROR("RIFF size is too small.");
    605     return WEBP_INFO_PARSE_ERROR;
    606   }
    607   if (riff_size > MAX_CHUNK_PAYLOAD) {
    608     LOG_ERROR("RIFF size is over limit.");
    609     return WEBP_INFO_PARSE_ERROR;
    610   }
    611   riff_size += CHUNK_HEADER_SIZE;
    612   if (!webp_info->quiet) {
    613     printf("RIFF HEADER:\n");
    614     printf("  File size: %6d\n", (int)riff_size);
    615   }
    616   if (riff_size < mem->end) {
    617     LOG_WARN("RIFF size is smaller than the file size.");
    618     mem->end = riff_size;
    619   } else if (riff_size > mem->end) {
    620     LOG_ERROR("Truncated data detected when parsing RIFF payload.");
    621     return WEBP_INFO_TRUNCATED_DATA;
    622   }
    623   Skip(mem, RIFF_HEADER_SIZE);
    624   return WEBP_INFO_OK;
    625 }
    626 
    627 static WebPInfoStatus ParseChunk(const WebPInfo* const webp_info,
    628                                  MemBuffer* const mem,
    629                                  ChunkData* const chunk_data) {
    630   memset(chunk_data, 0, sizeof(*chunk_data));
    631   if (MemDataSize(mem) < CHUNK_HEADER_SIZE) {
    632     LOG_ERROR("Truncated data detected when parsing chunk header.");
    633     return WEBP_INFO_TRUNCATED_DATA;
    634   } else {
    635     const size_t chunk_start_offset = mem->start;
    636     const uint32_t fourcc = ReadMemBufLE32(mem);
    637     const uint32_t payload_size = ReadMemBufLE32(mem);
    638     const uint32_t payload_size_padded = payload_size + (payload_size & 1);
    639     const size_t chunk_size = CHUNK_HEADER_SIZE + payload_size_padded;
    640     int i;
    641     if (payload_size > MAX_CHUNK_PAYLOAD) {
    642       LOG_ERROR("Size of chunk payload is over limit.");
    643       return WEBP_INFO_INVALID_PARAM;
    644     }
    645     if (payload_size_padded > MemDataSize(mem)){
    646       LOG_ERROR("Truncated data detected when parsing chunk payload.");
    647       return WEBP_INFO_TRUNCATED_DATA;
    648     }
    649     for (i = 0; i < CHUNK_TYPES; ++i) {
    650       if (kWebPChunkTags[i] == fourcc) break;
    651     }
    652     chunk_data->offset = chunk_start_offset;
    653     chunk_data->size = chunk_size;
    654     chunk_data->id = (ChunkID)i;
    655     chunk_data->payload = GetBuffer(mem);
    656     if (chunk_data->id == CHUNK_ANMF) {
    657       if (payload_size != payload_size_padded) {
    658         LOG_ERROR("ANMF chunk size should always be even.");
    659         return WEBP_INFO_PARSE_ERROR;
    660       }
    661       // There are sub-chunks to be parsed in an ANMF chunk.
    662       Skip(mem, ANMF_CHUNK_SIZE);
    663     } else {
    664       Skip(mem, payload_size_padded);
    665     }
    666     return WEBP_INFO_OK;
    667   }
    668 }
    669 
    670 // -----------------------------------------------------------------------------
    671 // Chunk analysis.
    672 
    673 static WebPInfoStatus ProcessVP8XChunk(const ChunkData* const chunk_data,
    674                                        WebPInfo* const webp_info) {
    675   const uint8_t* data = chunk_data->payload;
    676   if (webp_info->chunk_counts[CHUNK_VP8] ||
    677       webp_info->chunk_counts[CHUNK_VP8L] ||
    678       webp_info->chunk_counts[CHUNK_VP8X]) {
    679     LOG_ERROR("Already seen a VP8/VP8L/VP8X chunk when parsing VP8X chunk.");
    680     return WEBP_INFO_PARSE_ERROR;
    681   }
    682   if (chunk_data->size != VP8X_CHUNK_SIZE + CHUNK_HEADER_SIZE) {
    683     LOG_ERROR("Corrupted VP8X chunk.");
    684     return WEBP_INFO_PARSE_ERROR;
    685   }
    686   ++webp_info->chunk_counts[CHUNK_VP8X];
    687   webp_info->feature_flags = *data;
    688   data += 4;
    689   webp_info->canvas_width = 1 + ReadLE24(&data);
    690   webp_info->canvas_height = 1 + ReadLE24(&data);
    691   if (!webp_info->quiet) {
    692     printf("  ICCP: %d\n  Alpha: %d\n  EXIF: %d\n  XMP: %d\n  Animation: %d\n",
    693            (webp_info->feature_flags & ICCP_FLAG) != 0,
    694            (webp_info->feature_flags & ALPHA_FLAG) != 0,
    695            (webp_info->feature_flags & EXIF_FLAG) != 0,
    696            (webp_info->feature_flags & XMP_FLAG) != 0,
    697            (webp_info->feature_flags & ANIMATION_FLAG) != 0);
    698     printf("  Canvas size %d x %d\n",
    699            webp_info->canvas_width, webp_info->canvas_height);
    700   }
    701   if (webp_info->canvas_width > MAX_CANVAS_SIZE) {
    702     LOG_WARN("Canvas width is out of range in VP8X chunk.");
    703   }
    704   if (webp_info->canvas_height > MAX_CANVAS_SIZE) {
    705     LOG_WARN("Canvas height is out of range in VP8X chunk.");
    706   }
    707   if ((uint64_t)webp_info->canvas_width * webp_info->canvas_height >
    708       MAX_IMAGE_AREA) {
    709     LOG_WARN("Canvas area is out of range in VP8X chunk.");
    710   }
    711   return WEBP_INFO_OK;
    712 }
    713 
    714 static WebPInfoStatus ProcessANIMChunk(const ChunkData* const chunk_data,
    715                                        WebPInfo* const webp_info) {
    716   const uint8_t* data = chunk_data->payload;
    717   if (!webp_info->chunk_counts[CHUNK_VP8X]) {
    718     LOG_ERROR("ANIM chunk detected before VP8X chunk.");
    719     return WEBP_INFO_PARSE_ERROR;
    720   }
    721   if (chunk_data->size != ANIM_CHUNK_SIZE + CHUNK_HEADER_SIZE) {
    722     LOG_ERROR("Corrupted ANIM chunk.");
    723     return WEBP_INFO_PARSE_ERROR;
    724   }
    725   webp_info->bgcolor = ReadLE32(&data);
    726   webp_info->loop_count = ReadLE16(&data);
    727   ++webp_info->chunk_counts[CHUNK_ANIM];
    728   if (!webp_info->quiet) {
    729     printf("  Background color:(ARGB) %02x %02x %02x %02x\n",
    730            (webp_info->bgcolor >> 24) & 0xff,
    731            (webp_info->bgcolor >> 16) & 0xff,
    732            (webp_info->bgcolor >> 8) & 0xff,
    733            webp_info->bgcolor & 0xff);
    734     printf("  Loop count      : %d\n", webp_info->loop_count);
    735   }
    736   if (webp_info->loop_count > MAX_LOOP_COUNT) {
    737     LOG_WARN("Loop count is out of range in ANIM chunk.");
    738   }
    739   return WEBP_INFO_OK;
    740 }
    741 
    742 static WebPInfoStatus ProcessANMFChunk(const ChunkData* const chunk_data,
    743                                        WebPInfo* const webp_info) {
    744   const uint8_t* data = chunk_data->payload;
    745   int offset_x, offset_y, width, height, duration, blend, dispose, temp;
    746   if (webp_info->is_processing_anim_frame) {
    747     LOG_ERROR("ANMF chunk detected within another ANMF chunk.");
    748     return WEBP_INFO_PARSE_ERROR;
    749   }
    750   if (!webp_info->chunk_counts[CHUNK_ANIM]) {
    751     LOG_ERROR("ANMF chunk detected before ANIM chunk.");
    752     return WEBP_INFO_PARSE_ERROR;
    753   }
    754   if (chunk_data->size <= CHUNK_HEADER_SIZE + ANMF_CHUNK_SIZE) {
    755     LOG_ERROR("Truncated data detected when parsing ANMF chunk.");
    756     return WEBP_INFO_TRUNCATED_DATA;
    757   }
    758   offset_x = 2 * ReadLE24(&data);
    759   offset_y = 2 * ReadLE24(&data);
    760   width = 1 + ReadLE24(&data);
    761   height = 1 + ReadLE24(&data);
    762   duration = ReadLE24(&data);
    763   temp = *data;
    764   dispose = temp & 1;
    765   blend = (temp >> 1) & 1;
    766   ++webp_info->chunk_counts[CHUNK_ANMF];
    767   if (!webp_info->quiet) {
    768     printf("  Offset_X: %d\n  Offset_Y: %d\n  Width: %d\n  Height: %d\n"
    769            "  Duration: %d\n  Dispose: %d\n  Blend: %d\n",
    770            offset_x, offset_y, width, height, duration, dispose, blend);
    771   }
    772   if (duration > MAX_DURATION) {
    773     LOG_ERROR("Invalid duration parameter in ANMF chunk.");
    774     return WEBP_INFO_INVALID_PARAM;
    775   }
    776   if (offset_x > MAX_POSITION_OFFSET || offset_y > MAX_POSITION_OFFSET) {
    777     LOG_ERROR("Invalid offset parameters in ANMF chunk.");
    778     return WEBP_INFO_INVALID_PARAM;
    779   }
    780   if ((uint64_t)offset_x + width > (uint64_t)webp_info->canvas_width ||
    781       (uint64_t)offset_y + height > (uint64_t)webp_info->canvas_height) {
    782     LOG_ERROR("Frame exceeds canvas in ANMF chunk.");
    783     return WEBP_INFO_INVALID_PARAM;
    784   }
    785   webp_info->is_processing_anim_frame = 1;
    786   webp_info->seen_alpha_subchunk = 0;
    787   webp_info->seen_image_subchunk = 0;
    788   webp_info->frame_width = width;
    789   webp_info->frame_height = height;
    790   webp_info->anim_frame_data_size =
    791       chunk_data->size - CHUNK_HEADER_SIZE - ANMF_CHUNK_SIZE;
    792   return WEBP_INFO_OK;
    793 }
    794 
    795 static WebPInfoStatus ProcessImageChunk(const ChunkData* const chunk_data,
    796                                         WebPInfo* const webp_info) {
    797   const uint8_t* data = chunk_data->payload - CHUNK_HEADER_SIZE;
    798   WebPBitstreamFeatures features;
    799   const VP8StatusCode vp8_status =
    800       WebPGetFeatures(data, chunk_data->size, &features);
    801   if (vp8_status != VP8_STATUS_OK) {
    802     LOG_ERROR("VP8/VP8L bitstream error.");
    803     return WEBP_INFO_BITSTREAM_ERROR;
    804   }
    805   if (!webp_info->quiet) {
    806     assert(features.format >= 0 && features.format <= 2);
    807     printf("  Width: %d\n  Height: %d\n  Alpha: %d\n  Animation: %d\n"
    808            "  Format: %s (%d)\n",
    809            features.width, features.height, features.has_alpha,
    810            features.has_animation, kFormats[features.format], features.format);
    811   }
    812   if (webp_info->is_processing_anim_frame) {
    813     ++webp_info->anmf_subchunk_counts[chunk_data->id == CHUNK_VP8 ? 0 : 1];
    814     if (chunk_data->id == CHUNK_VP8L && webp_info->seen_alpha_subchunk) {
    815       LOG_ERROR("Both VP8L and ALPH sub-chunks are present in an ANMF chunk.");
    816       return WEBP_INFO_PARSE_ERROR;
    817     }
    818     if (webp_info->frame_width != features.width ||
    819         webp_info->frame_height != features.height) {
    820       LOG_ERROR("Frame size in VP8/VP8L sub-chunk differs from ANMF header.");
    821       return WEBP_INFO_PARSE_ERROR;
    822     }
    823     if (webp_info->seen_image_subchunk) {
    824       LOG_ERROR("Consecutive VP8/VP8L sub-chunks in an ANMF chunk.");
    825       return WEBP_INFO_PARSE_ERROR;
    826     }
    827     webp_info->seen_image_subchunk = 1;
    828   } else {
    829     if (webp_info->chunk_counts[CHUNK_VP8] ||
    830         webp_info->chunk_counts[CHUNK_VP8L]) {
    831       LOG_ERROR("Multiple VP8/VP8L chunks detected.");
    832       return WEBP_INFO_PARSE_ERROR;
    833     }
    834     if (chunk_data->id == CHUNK_VP8L &&
    835         webp_info->chunk_counts[CHUNK_ALPHA]) {
    836       LOG_WARN("Both VP8L and ALPH chunks are detected.");
    837     }
    838     if (webp_info->chunk_counts[CHUNK_ANIM] ||
    839         webp_info->chunk_counts[CHUNK_ANMF]) {
    840       LOG_ERROR("VP8/VP8L chunk and ANIM/ANMF chunk are both detected.");
    841       return WEBP_INFO_PARSE_ERROR;
    842     }
    843     if (webp_info->chunk_counts[CHUNK_VP8X]) {
    844       if (webp_info->canvas_width != features.width ||
    845           webp_info->canvas_height != features.height) {
    846         LOG_ERROR("Image size in VP8/VP8L chunk differs from VP8X chunk.");
    847         return WEBP_INFO_PARSE_ERROR;
    848       }
    849     } else {
    850       webp_info->canvas_width = features.width;
    851       webp_info->canvas_height = features.height;
    852       if (webp_info->canvas_width < 1 || webp_info->canvas_height < 1 ||
    853           webp_info->canvas_width > MAX_CANVAS_SIZE ||
    854           webp_info->canvas_height > MAX_CANVAS_SIZE ||
    855           (uint64_t)webp_info->canvas_width * webp_info->canvas_height >
    856               MAX_IMAGE_AREA) {
    857         LOG_WARN("Invalid parameters in VP8/VP8L chunk.");
    858       }
    859     }
    860     ++webp_info->chunk_counts[chunk_data->id];
    861   }
    862   ++webp_info->num_frames;
    863   webp_info->has_alpha |= features.has_alpha;
    864   if (webp_info->parse_bitstream) {
    865     const int is_lossy = (chunk_data->id == CHUNK_VP8);
    866     const WebPInfoStatus status =
    867         is_lossy ? ParseLossyHeader(chunk_data, webp_info)
    868                  : ParseLosslessHeader(chunk_data, webp_info);
    869     if (status != WEBP_INFO_OK) return status;
    870   }
    871   return WEBP_INFO_OK;
    872 }
    873 
    874 static WebPInfoStatus ProcessALPHChunk(const ChunkData* const chunk_data,
    875                                        WebPInfo* const webp_info) {
    876   if (webp_info->is_processing_anim_frame) {
    877     ++webp_info->anmf_subchunk_counts[2];
    878     if (webp_info->seen_alpha_subchunk) {
    879       LOG_ERROR("Consecutive ALPH sub-chunks in an ANMF chunk.");
    880       return WEBP_INFO_PARSE_ERROR;
    881     }
    882     webp_info->seen_alpha_subchunk = 1;
    883 
    884     if (webp_info->seen_image_subchunk) {
    885       LOG_ERROR("ALPHA sub-chunk detected after VP8 sub-chunk "
    886                 "in an ANMF chunk.");
    887       return WEBP_INFO_PARSE_ERROR;
    888     }
    889   } else {
    890     if (webp_info->chunk_counts[CHUNK_ANIM] ||
    891         webp_info->chunk_counts[CHUNK_ANMF]) {
    892       LOG_ERROR("ALPHA chunk and ANIM/ANMF chunk are both detected.");
    893       return WEBP_INFO_PARSE_ERROR;
    894     }
    895     if (!webp_info->chunk_counts[CHUNK_VP8X]) {
    896       LOG_ERROR("ALPHA chunk detected before VP8X chunk.");
    897       return WEBP_INFO_PARSE_ERROR;
    898     }
    899     if (webp_info->chunk_counts[CHUNK_VP8]) {
    900       LOG_ERROR("ALPHA chunk detected after VP8 chunk.");
    901       return WEBP_INFO_PARSE_ERROR;
    902     }
    903     if (webp_info->chunk_counts[CHUNK_ALPHA]) {
    904       LOG_ERROR("Multiple ALPHA chunks detected.");
    905       return WEBP_INFO_PARSE_ERROR;
    906     }
    907     ++webp_info->chunk_counts[CHUNK_ALPHA];
    908   }
    909   webp_info->has_alpha = 1;
    910   if (webp_info->parse_bitstream) {
    911     const WebPInfoStatus status = ParseAlphaHeader(chunk_data, webp_info);
    912     if (status != WEBP_INFO_OK) return status;
    913   }
    914   return WEBP_INFO_OK;
    915 }
    916 
    917 static WebPInfoStatus ProcessICCPChunk(const ChunkData* const chunk_data,
    918                                        WebPInfo* const webp_info) {
    919   (void)chunk_data;
    920   if (!webp_info->chunk_counts[CHUNK_VP8X]) {
    921     LOG_ERROR("ICCP chunk detected before VP8X chunk.");
    922     return WEBP_INFO_PARSE_ERROR;
    923   }
    924   if (webp_info->chunk_counts[CHUNK_VP8] ||
    925       webp_info->chunk_counts[CHUNK_VP8L] ||
    926       webp_info->chunk_counts[CHUNK_ANIM]) {
    927     LOG_ERROR("ICCP chunk detected after image data.");
    928     return WEBP_INFO_PARSE_ERROR;
    929   }
    930   ++webp_info->chunk_counts[CHUNK_ICCP];
    931   return WEBP_INFO_OK;
    932 }
    933 
    934 static WebPInfoStatus ProcessChunk(const ChunkData* const chunk_data,
    935                                    WebPInfo* const webp_info) {
    936   WebPInfoStatus status = WEBP_INFO_OK;
    937   ChunkID id = chunk_data->id;
    938   if (chunk_data->id == CHUNK_UNKNOWN) {
    939     char error_message[50];
    940     snprintf(error_message, 50, "Unknown chunk at offset %6d, length %6d",
    941             (int)chunk_data->offset, (int)chunk_data->size);
    942     LOG_WARN(error_message);
    943   } else {
    944     if (!webp_info->quiet) {
    945       char tag[4];
    946       uint32_t fourcc = kWebPChunkTags[chunk_data->id];
    947 #ifdef WORDS_BIGENDIAN
    948       fourcc = (fourcc >> 24) | ((fourcc >> 8) & 0xff00) |
    949                ((fourcc << 8) & 0xff0000) | (fourcc << 24);
    950 #endif
    951       memcpy(tag, &fourcc, sizeof(tag));
    952       printf("Chunk %c%c%c%c at offset %6d, length %6d\n",
    953              tag[0], tag[1], tag[2], tag[3], (int)chunk_data->offset,
    954              (int)chunk_data->size);
    955     }
    956   }
    957   switch (id) {
    958     case CHUNK_VP8:
    959     case CHUNK_VP8L:
    960       status = ProcessImageChunk(chunk_data, webp_info);
    961       break;
    962     case CHUNK_VP8X:
    963       status = ProcessVP8XChunk(chunk_data, webp_info);
    964       break;
    965     case CHUNK_ALPHA:
    966       status = ProcessALPHChunk(chunk_data, webp_info);
    967       break;
    968     case CHUNK_ANIM:
    969       status = ProcessANIMChunk(chunk_data, webp_info);
    970       break;
    971     case CHUNK_ANMF:
    972       status = ProcessANMFChunk(chunk_data, webp_info);
    973       break;
    974     case CHUNK_ICCP:
    975       status = ProcessICCPChunk(chunk_data, webp_info);
    976       break;
    977     case CHUNK_EXIF:
    978     case CHUNK_XMP:
    979       ++webp_info->chunk_counts[id];
    980       break;
    981     case CHUNK_UNKNOWN:
    982     default:
    983       break;
    984   }
    985   if (webp_info->is_processing_anim_frame && id != CHUNK_ANMF) {
    986     if (webp_info->anim_frame_data_size == chunk_data->size) {
    987       if (!webp_info->seen_image_subchunk) {
    988         LOG_ERROR("No VP8/VP8L chunk detected in an ANMF chunk.");
    989         return WEBP_INFO_PARSE_ERROR;
    990       }
    991       webp_info->is_processing_anim_frame = 0;
    992     } else if (webp_info->anim_frame_data_size > chunk_data->size) {
    993       webp_info->anim_frame_data_size -= chunk_data->size;
    994     } else {
    995       LOG_ERROR("Truncated data detected when parsing ANMF chunk.");
    996       return WEBP_INFO_TRUNCATED_DATA;
    997     }
    998   }
    999   return status;
   1000 }
   1001 
   1002 static WebPInfoStatus Validate(WebPInfo* const webp_info) {
   1003   if (webp_info->num_frames < 1) {
   1004     LOG_ERROR("No image/frame detected.");
   1005     return WEBP_INFO_MISSING_DATA;
   1006   }
   1007   if (webp_info->chunk_counts[CHUNK_VP8X]) {
   1008     const int iccp = !!(webp_info->feature_flags & ICCP_FLAG);
   1009     const int exif = !!(webp_info->feature_flags & EXIF_FLAG);
   1010     const int xmp = !!(webp_info->feature_flags & XMP_FLAG);
   1011     const int animation = !!(webp_info->feature_flags & ANIMATION_FLAG);
   1012     const int alpha = !!(webp_info->feature_flags & ALPHA_FLAG);
   1013     if (!alpha && webp_info->has_alpha) {
   1014       LOG_ERROR("Unexpected alpha data detected.");
   1015       return WEBP_INFO_PARSE_ERROR;
   1016     }
   1017     if (alpha && !webp_info->has_alpha) {
   1018       LOG_WARN("Alpha flag is set with no alpha data present.");
   1019     }
   1020     if (iccp && !webp_info->chunk_counts[CHUNK_ICCP]) {
   1021       LOG_ERROR("Missing ICCP chunk.");
   1022       return WEBP_INFO_MISSING_DATA;
   1023     }
   1024     if (exif && !webp_info->chunk_counts[CHUNK_EXIF]) {
   1025       LOG_ERROR("Missing EXIF chunk.");
   1026       return WEBP_INFO_MISSING_DATA;
   1027     }
   1028     if (xmp && !webp_info->chunk_counts[CHUNK_XMP]) {
   1029       LOG_ERROR("Missing XMP chunk.");
   1030       return WEBP_INFO_MISSING_DATA;
   1031     }
   1032     if (!iccp && webp_info->chunk_counts[CHUNK_ICCP]) {
   1033       LOG_ERROR("Unexpected ICCP chunk detected.");
   1034       return WEBP_INFO_PARSE_ERROR;
   1035     }
   1036     if (!exif && webp_info->chunk_counts[CHUNK_EXIF]) {
   1037       LOG_ERROR("Unexpected EXIF chunk detected.");
   1038       return WEBP_INFO_PARSE_ERROR;
   1039     }
   1040     if (!xmp && webp_info->chunk_counts[CHUNK_XMP]) {
   1041       LOG_ERROR("Unexpected XMP chunk detected.");
   1042       return WEBP_INFO_PARSE_ERROR;
   1043     }
   1044     // Incomplete animation frame.
   1045     if (webp_info->is_processing_anim_frame) return WEBP_INFO_MISSING_DATA;
   1046     if (!animation && webp_info->num_frames > 1) {
   1047       LOG_ERROR("More than 1 frame detected in non-animation file.");
   1048       return WEBP_INFO_PARSE_ERROR;
   1049     }
   1050     if (animation && (!webp_info->chunk_counts[CHUNK_ANIM] ||
   1051         !webp_info->chunk_counts[CHUNK_ANMF])) {
   1052       LOG_ERROR("No ANIM/ANMF chunk detected in animation file.");
   1053       return WEBP_INFO_PARSE_ERROR;
   1054     }
   1055   }
   1056   return WEBP_INFO_OK;
   1057 }
   1058 
   1059 static void ShowSummary(const WebPInfo* const webp_info) {
   1060   int i;
   1061   printf("Summary:\n");
   1062   printf("Number of frames: %d\n", webp_info->num_frames);
   1063   printf("Chunk type  :  VP8 VP8L VP8X ALPH ANIM ANMF(VP8 /VP8L/ALPH) ICCP "
   1064       "EXIF  XMP\n");
   1065   printf("Chunk counts: ");
   1066   for (i = 0; i < CHUNK_TYPES; ++i) {
   1067     printf("%4d ", webp_info->chunk_counts[i]);
   1068     if (i == CHUNK_ANMF) {
   1069       printf("%4d %4d %4d  ",
   1070              webp_info->anmf_subchunk_counts[0],
   1071              webp_info->anmf_subchunk_counts[1],
   1072              webp_info->anmf_subchunk_counts[2]);
   1073     }
   1074   }
   1075   printf("\n");
   1076 }
   1077 
   1078 static WebPInfoStatus AnalyzeWebP(WebPInfo* const webp_info,
   1079                                   const WebPData* webp_data) {
   1080   ChunkData chunk_data;
   1081   MemBuffer mem_buffer;
   1082   WebPInfoStatus webp_info_status = WEBP_INFO_OK;
   1083 
   1084   InitMemBuffer(&mem_buffer, webp_data);
   1085   webp_info_status = ParseRIFFHeader(webp_info, &mem_buffer);
   1086   if (webp_info_status != WEBP_INFO_OK) goto Error;
   1087 
   1088   //  Loop through all the chunks. Terminate immediately in case of error.
   1089   while (webp_info_status == WEBP_INFO_OK && MemDataSize(&mem_buffer) > 0) {
   1090     webp_info_status = ParseChunk(webp_info, &mem_buffer, &chunk_data);
   1091     if (webp_info_status != WEBP_INFO_OK) goto Error;
   1092     webp_info_status = ProcessChunk(&chunk_data, webp_info);
   1093   }
   1094   if (webp_info_status != WEBP_INFO_OK) goto Error;
   1095   if (webp_info->show_summary) ShowSummary(webp_info);
   1096 
   1097   //  Final check.
   1098   webp_info_status = Validate(webp_info);
   1099 
   1100  Error:
   1101   if (!webp_info->quiet) {
   1102     if (webp_info_status == WEBP_INFO_OK) {
   1103       printf("No error detected.\n");
   1104     } else {
   1105       printf("Errors detected.\n");
   1106     }
   1107     if (webp_info->num_warnings > 0) {
   1108       printf("There were %d warning(s).\n", webp_info->num_warnings);
   1109     }
   1110   }
   1111   return webp_info_status;
   1112 }
   1113 
   1114 static void Help(void) {
   1115   printf("Usage: webpinfo [options] in_files\n"
   1116          "Note: there could be multiple input files;\n"
   1117          "      options must come before input files.\n"
   1118          "Options:\n"
   1119          "  -version ........... Print version number and exit.\n"
   1120          "  -quiet ............. Do not show chunk parsing information.\n"
   1121          "  -diag .............. Show parsing error diagnosis.\n"
   1122          "  -summary ........... Show chunk stats summary.\n"
   1123          "  -bitstream_info .... Parse bitstream header.\n");
   1124 }
   1125 
   1126 // Returns EXIT_SUCCESS on success, EXIT_FAILURE on failure.
   1127 int main(int argc, const char* argv[]) {
   1128   int c, quiet = 0, show_diag = 0, show_summary = 0;
   1129   int parse_bitstream = 0;
   1130   WebPInfoStatus webp_info_status = WEBP_INFO_OK;
   1131   WebPInfo webp_info;
   1132 
   1133   INIT_WARGV(argc, argv);
   1134 
   1135   if (argc == 1) {
   1136     Help();
   1137     FREE_WARGV_AND_RETURN(EXIT_FAILURE);
   1138   }
   1139 
   1140   // Parse command-line input.
   1141   for (c = 1; c < argc; ++c) {
   1142     if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help") ||
   1143         !strcmp(argv[c], "-H") || !strcmp(argv[c], "-longhelp")) {
   1144       Help();
   1145       FREE_WARGV_AND_RETURN(EXIT_SUCCESS);
   1146     } else if (!strcmp(argv[c], "-quiet")) {
   1147       quiet = 1;
   1148     } else if (!strcmp(argv[c], "-diag")) {
   1149       show_diag = 1;
   1150     } else if (!strcmp(argv[c], "-summary")) {
   1151       show_summary = 1;
   1152     } else if (!strcmp(argv[c], "-bitstream_info")) {
   1153       parse_bitstream = 1;
   1154     } else if (!strcmp(argv[c], "-version")) {
   1155       const int version = WebPGetDecoderVersion();
   1156       printf("WebP Decoder version: %d.%d.%d\n",
   1157              (version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff);
   1158       FREE_WARGV_AND_RETURN(EXIT_SUCCESS);
   1159     } else {  // Assume the remaining are all input files.
   1160       break;
   1161     }
   1162   }
   1163 
   1164   if (c == argc) {
   1165     Help();
   1166     FREE_WARGV_AND_RETURN(EXIT_FAILURE);
   1167   }
   1168 
   1169   // Process input files one by one.
   1170   for (; c < argc; ++c) {
   1171     WebPData webp_data;
   1172     const W_CHAR* in_file = NULL;
   1173     WebPInfoInit(&webp_info);
   1174     webp_info.quiet = quiet;
   1175     webp_info.show_diagnosis = show_diag;
   1176     webp_info.show_summary = show_summary;
   1177     webp_info.parse_bitstream = parse_bitstream;
   1178     in_file = GET_WARGV(argv, c);
   1179     if (in_file == NULL ||
   1180         !ReadFileToWebPData((const char*)in_file, &webp_data)) {
   1181       webp_info_status = WEBP_INFO_INVALID_COMMAND;
   1182       WFPRINTF(stderr, "Failed to open input file %s.\n", in_file);
   1183       continue;
   1184     }
   1185     if (!webp_info.quiet) WPRINTF("File: %s\n", in_file);
   1186     webp_info_status = AnalyzeWebP(&webp_info, &webp_data);
   1187     WebPDataClear(&webp_data);
   1188   }
   1189   FREE_WARGV_AND_RETURN((webp_info_status == WEBP_INFO_OK) ? EXIT_SUCCESS
   1190                                                            : EXIT_FAILURE);
   1191 }