| // Copyright 2017 Google Inc. All Rights Reserved. |
| // |
| // Use of this source code is governed by a BSD-style license |
| // that can be found in the COPYING file in the root of the source |
| // tree. An additional intellectual property rights grant can be found |
| // in the file PATENTS. All contributing project authors may |
| // be found in the AUTHORS file in the root of the source tree. |
| // ----------------------------------------------------------------------------- |
| // |
| // Command-line tool to print out the chunk level structure of WebP files |
| // along with basic integrity checks. |
| // |
| // Author: Hui Su (huisu@google.com) |
| |
| #include <assert.h> |
| #include <stdio.h> |
| |
| #ifdef HAVE_CONFIG_H |
| #include "webp/config.h" |
| #endif |
| |
| #include "../imageio/imageio_util.h" |
| #include "webp/decode.h" |
| #include "webp/format_constants.h" |
| #include "webp/mux_types.h" |
| |
| #if defined(_MSC_VER) && _MSC_VER < 1900 |
| #define snprintf _snprintf |
| #endif |
| |
| #define LOG_ERROR(MESSAGE) \ |
| do { \ |
| if (webp_info->show_diagnosis_) { \ |
| fprintf(stderr, "Error: %s\n", MESSAGE); \ |
| } \ |
| } while (0) |
| |
| #define LOG_WARN(MESSAGE) \ |
| do { \ |
| if (webp_info->show_diagnosis_) { \ |
| fprintf(stderr, "Warning: %s\n", MESSAGE); \ |
| } \ |
| } while (0) |
| |
| static const char* const kFormats[3] = { |
| "Unknown", |
| "Lossy", |
| "Lossless" |
| }; |
| |
| static const char* const kLosslessTransforms[4] = { |
| "Predictor", |
| "Cross Color", |
| "Subtract Green", |
| "Color Indexing" |
| }; |
| |
| static const char* const kAlphaFilterMethods[4] = { |
| "None", |
| "Horizontal", |
| "Vertical", |
| "Gradient" |
| }; |
| |
| typedef enum { |
| WEBP_INFO_OK = 0, |
| WEBP_INFO_TRUNCATED_DATA, |
| WEBP_INFO_PARSE_ERROR, |
| WEBP_INFO_INVALID_PARAM, |
| WEBP_INFO_BITSTREAM_ERROR, |
| WEBP_INFO_MISSING_DATA, |
| WEBP_INFO_INVALID_COMMAND |
| } WebPInfoStatus; |
| |
| typedef enum ChunkID { |
| CHUNK_VP8, |
| CHUNK_VP8L, |
| CHUNK_VP8X, |
| CHUNK_ALPHA, |
| CHUNK_ANIM, |
| CHUNK_ANMF, |
| CHUNK_ICCP, |
| CHUNK_EXIF, |
| CHUNK_XMP, |
| CHUNK_UNKNOWN, |
| CHUNK_TYPES = CHUNK_UNKNOWN |
| } ChunkID; |
| |
| typedef struct { |
| size_t start_; |
| size_t end_; |
| const uint8_t* buf_; |
| } MemBuffer; |
| |
| typedef struct { |
| size_t offset_; |
| size_t size_; |
| const uint8_t* payload_; |
| ChunkID id_; |
| } ChunkData; |
| |
| typedef struct WebPInfo { |
| int canvas_width_; |
| int canvas_height_; |
| int loop_count_; |
| int num_frames_; |
| int chunk_counts_[CHUNK_TYPES]; |
| int anmf_subchunk_counts_[3]; // 0 VP8; 1 VP8L; 2 ALPH. |
| uint32_t bgcolor_; |
| int feature_flags_; |
| int has_alpha_; |
| // Used for parsing ANMF chunks. |
| int frame_width_, frame_height_; |
| size_t anim_frame_data_size_; |
| int is_processing_anim_frame_, seen_alpha_subchunk_, seen_image_subchunk_; |
| // Print output control. |
| int quiet_, show_diagnosis_, show_summary_; |
| int parse_bitstream_; |
| } WebPInfo; |
| |
| static void WebPInfoInit(WebPInfo* const webp_info) { |
| memset(webp_info, 0, sizeof(*webp_info)); |
| } |
| |
| static const char kWebPChunkTags[CHUNK_TYPES][4] = { |
| { 'V', 'P', '8', ' ' }, |
| { 'V', 'P', '8', 'L' }, |
| { 'V', 'P', '8', 'X' }, |
| { 'A', 'L', 'P', 'H' }, |
| { 'A', 'N', 'I', 'M' }, |
| { 'A', 'N', 'M', 'F' }, |
| { 'I', 'C', 'C', 'P' }, |
| { 'E', 'X', 'I', 'F' }, |
| { 'X', 'M', 'P', ' ' }, |
| }; |
| |
| // ----------------------------------------------------------------------------- |
| // Data reading. |
| |
| static int GetLE16(const uint8_t* const data) { |
| return (data[0] << 0) | (data[1] << 8); |
| } |
| |
| static int GetLE24(const uint8_t* const data) { |
| return GetLE16(data) | (data[2] << 16); |
| } |
| |
| static uint32_t GetLE32(const uint8_t* const data) { |
| return GetLE16(data) | ((uint32_t)GetLE16(data + 2) << 16); |
| } |
| |
| static int ReadLE16(const uint8_t** data) { |
| const int val = GetLE16(*data); |
| *data += 2; |
| return val; |
| } |
| |
| static int ReadLE24(const uint8_t** data) { |
| const int val = GetLE24(*data); |
| *data += 3; |
| return val; |
| } |
| |
| static uint32_t ReadLE32(const uint8_t** data) { |
| const uint32_t val = GetLE32(*data); |
| *data += 4; |
| return val; |
| } |
| |
| static int ReadFileToWebPData(const char* const filename, |
| WebPData* const webp_data) { |
| const uint8_t* data; |
| size_t size; |
| if (!ImgIoUtilReadFile(filename, &data, &size)) return 0; |
| webp_data->bytes = data; |
| webp_data->size = size; |
| return 1; |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // MemBuffer object. |
| |
| static void InitMemBuffer(MemBuffer* const mem, const WebPData* webp_data) { |
| mem->buf_ = webp_data->bytes; |
| mem->start_ = 0; |
| mem->end_ = webp_data->size; |
| } |
| |
| static size_t MemDataSize(const MemBuffer* const mem) { |
| return (mem->end_ - mem->start_); |
| } |
| |
| static const uint8_t* GetBuffer(MemBuffer* const mem) { |
| return mem->buf_ + mem->start_; |
| } |
| |
| static void Skip(MemBuffer* const mem, size_t size) { |
| mem->start_ += size; |
| } |
| |
| static uint32_t ReadMemBufLE32(MemBuffer* const mem) { |
| const uint8_t* const data = mem->buf_ + mem->start_; |
| const uint32_t val = GetLE32(data); |
| assert(MemDataSize(mem) >= 4); |
| Skip(mem, 4); |
| return val; |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // Lossy bitstream analysis. |
| |
| static int GetBits(const uint8_t* const data, size_t data_size, size_t nb, |
| int* val, uint64_t* const bit_pos) { |
| *val = 0; |
| while (nb-- > 0) { |
| const uint64_t p = (*bit_pos)++; |
| if ((p >> 3) >= data_size) { |
| return 0; |
| } else { |
| const int bit = !!(data[p >> 3] & (128 >> ((p & 7)))); |
| *val = (*val << 1) | bit; |
| } |
| } |
| return 1; |
| } |
| |
| static int GetSignedBits(const uint8_t* const data, size_t data_size, size_t nb, |
| int* val, uint64_t* const bit_pos) { |
| int sign; |
| if (!GetBits(data, data_size, nb, val, bit_pos)) return 0; |
| if (!GetBits(data, data_size, 1, &sign, bit_pos)) return 0; |
| if (sign) *val = -(*val); |
| return 1; |
| } |
| |
| #define GET_BITS(v, n) \ |
| do { \ |
| if (!GetBits(data, data_size, n, &(v), bit_pos)) { \ |
| LOG_ERROR("Truncated lossy bitstream."); \ |
| return WEBP_INFO_TRUNCATED_DATA; \ |
| } \ |
| } while (0) |
| |
| #define GET_SIGNED_BITS(v, n) \ |
| do { \ |
| if (!GetSignedBits(data, data_size, n, &(v), bit_pos)) { \ |
| LOG_ERROR("Truncated lossy bitstream."); \ |
| return WEBP_INFO_TRUNCATED_DATA; \ |
| } \ |
| } while (0) |
| |
| static WebPInfoStatus ParseLossySegmentHeader(const WebPInfo* const webp_info, |
| const uint8_t* const data, |
| size_t data_size, |
| uint64_t* const bit_pos) { |
| int use_segment; |
| GET_BITS(use_segment, 1); |
| printf(" Use segment: %d\n", use_segment); |
| if (use_segment) { |
| int update_map, update_data; |
| GET_BITS(update_map, 1); |
| GET_BITS(update_data, 1); |
| printf(" Update map: %d\n" |
| " Update data: %d\n", |
| update_map, update_data); |
| if (update_data) { |
| int i, a_delta; |
| int quantizer[4] = {0, 0, 0, 0}; |
| int filter_strength[4] = {0, 0, 0, 0}; |
| GET_BITS(a_delta, 1); |
| printf(" Absolute delta: %d\n", a_delta); |
| for (i = 0; i < 4; ++i) { |
| int bit; |
| GET_BITS(bit, 1); |
| if (bit) GET_SIGNED_BITS(quantizer[i], 7); |
| } |
| for (i = 0; i < 4; ++i) { |
| int bit; |
| GET_BITS(bit, 1); |
| if (bit) GET_SIGNED_BITS(filter_strength[i], 6); |
| } |
| printf(" Quantizer: %d %d %d %d\n", quantizer[0], quantizer[1], |
| quantizer[2], quantizer[3]); |
| printf(" Filter strength: %d %d %d %d\n", filter_strength[0], |
| filter_strength[1], filter_strength[2], filter_strength[3]); |
| } |
| if (update_map) { |
| int i; |
| int prob_segment[3] = {255, 255, 255}; |
| for (i = 0; i < 3; ++i) { |
| int bit; |
| GET_BITS(bit, 1); |
| if (bit) GET_BITS(prob_segment[i], 8); |
| } |
| printf(" Prob segment: %d %d %d\n", |
| prob_segment[0], prob_segment[1], prob_segment[2]); |
| } |
| } |
| return WEBP_INFO_OK; |
| } |
| |
| static WebPInfoStatus ParseLossyFilterHeader(const WebPInfo* const webp_info, |
| const uint8_t* const data, |
| size_t data_size, |
| uint64_t* const bit_pos) { |
| int simple_filter, level, sharpness, use_lf_delta; |
| GET_BITS(simple_filter, 1); |
| GET_BITS(level, 6); |
| GET_BITS(sharpness, 3); |
| GET_BITS(use_lf_delta, 1); |
| printf(" Simple filter: %d\n", simple_filter); |
| printf(" Level: %d\n", level); |
| printf(" Sharpness: %d\n", sharpness); |
| printf(" Use lf delta: %d\n", use_lf_delta); |
| if (use_lf_delta) { |
| int update; |
| GET_BITS(update, 1); |
| printf(" Update lf delta: %d\n", update); |
| if (update) { |
| int i; |
| for (i = 0; i < 4 + 4; ++i) { |
| int temp; |
| GET_BITS(temp, 1); |
| if (temp) GET_BITS(temp, 7); |
| } |
| } |
| } |
| return WEBP_INFO_OK; |
| } |
| |
| static WebPInfoStatus ParseLossyHeader(const ChunkData* const chunk_data, |
| const WebPInfo* const webp_info) { |
| const uint8_t* data = chunk_data->payload_; |
| size_t data_size = chunk_data->size_ - CHUNK_HEADER_SIZE; |
| const uint32_t bits = (uint32_t)data[0] | (data[1] << 8) | (data[2] << 16); |
| const int key_frame = !(bits & 1); |
| const int profile = (bits >> 1) & 7; |
| const int display = (bits >> 4) & 1; |
| const uint32_t partition0_length = (bits >> 5); |
| WebPInfoStatus status = WEBP_INFO_OK; |
| uint64_t bit_position = 0; |
| uint64_t* const bit_pos = &bit_position; |
| int colorspace, clamp_type; |
| printf(" Parsing lossy bitstream...\n"); |
| // Calling WebPGetFeatures() in ProcessImageChunk() should ensure this. |
| assert(chunk_data->size_ >= CHUNK_HEADER_SIZE + 10); |
| if (profile > 3) { |
| LOG_ERROR("Unknown profile."); |
| return WEBP_INFO_BITSTREAM_ERROR; |
| } |
| if (!display) { |
| LOG_ERROR("Frame is not displayable."); |
| return WEBP_INFO_BITSTREAM_ERROR; |
| } |
| data += 3; |
| data_size -= 3; |
| printf(" Key frame: %s\n" |
| " Profile: %d\n" |
| " Display: %s\n" |
| " Part. 0 length: %d\n", |
| key_frame ? "Yes" : "No", profile, |
| display ? "Yes" : "No", partition0_length); |
| if (key_frame) { |
| if (!(data[0] == 0x9d && data[1] == 0x01 && data[2] == 0x2a)) { |
| LOG_ERROR("Invalid lossy bitstream signature."); |
| return WEBP_INFO_BITSTREAM_ERROR; |
| } |
| printf(" Width: %d\n" |
| " X scale: %d\n" |
| " Height: %d\n" |
| " Y scale: %d\n", |
| ((data[4] << 8) | data[3]) & 0x3fff, data[4] >> 6, |
| ((data[6] << 8) | data[5]) & 0x3fff, data[6] >> 6); |
| data += 7; |
| data_size -= 7; |
| } else { |
| LOG_ERROR("Non-keyframe detected in lossy bitstream."); |
| return WEBP_INFO_BITSTREAM_ERROR; |
| } |
| if (partition0_length >= data_size) { |
| LOG_ERROR("Bad partition length."); |
| return WEBP_INFO_BITSTREAM_ERROR; |
| } |
| GET_BITS(colorspace, 1); |
| GET_BITS(clamp_type, 1); |
| printf(" Color space: %d\n", colorspace); |
| printf(" Clamp type: %d\n", clamp_type); |
| status = ParseLossySegmentHeader(webp_info, data, data_size, bit_pos); |
| if (status != WEBP_INFO_OK) return status; |
| status = ParseLossyFilterHeader(webp_info, data, data_size, bit_pos); |
| if (status != WEBP_INFO_OK) return status; |
| { // Partition number and size. |
| const uint8_t* part_size = data + partition0_length; |
| int num_parts, i; |
| size_t part_data_size; |
| GET_BITS(num_parts, 2); |
| num_parts = 1 << num_parts; |
| if ((int)(data_size - partition0_length) < (num_parts - 1) * 3) { |
| LOG_ERROR("Truncated lossy bitstream."); |
| return WEBP_INFO_TRUNCATED_DATA; |
| } |
| part_data_size = data_size - partition0_length - (num_parts - 1) * 3; |
| printf(" Total partitions: %d\n", num_parts); |
| for (i = 1; i < num_parts; ++i) { |
| const size_t psize = |
| part_size[0] | (part_size[1] << 8) | (part_size[2] << 16); |
| if (psize > part_data_size) { |
| LOG_ERROR("Truncated partition."); |
| return WEBP_INFO_TRUNCATED_DATA; |
| } |
| printf(" Part. %d length: %d\n", i, (int)psize); |
| part_data_size -= psize; |
| part_size += 3; |
| } |
| } |
| // Quantizer. |
| { |
| int base_q, bit; |
| int dq_y1_dc = 0, dq_y2_dc = 0, dq_y2_ac = 0, dq_uv_dc = 0, dq_uv_ac = 0; |
| GET_BITS(base_q, 7); |
| GET_BITS(bit, 1); |
| if (bit) GET_SIGNED_BITS(dq_y1_dc, 4); |
| GET_BITS(bit, 1); |
| if (bit) GET_SIGNED_BITS(dq_y2_dc, 4); |
| GET_BITS(bit, 1); |
| if (bit) GET_SIGNED_BITS(dq_y2_ac, 4); |
| GET_BITS(bit, 1); |
| if (bit) GET_SIGNED_BITS(dq_uv_dc, 4); |
| GET_BITS(bit, 1); |
| if (bit) GET_SIGNED_BITS(dq_uv_ac, 4); |
| printf(" Base Q: %d\n", base_q); |
| printf(" DQ Y1 DC: %d\n", dq_y1_dc); |
| printf(" DQ Y2 DC: %d\n", dq_y2_dc); |
| printf(" DQ Y2 AC: %d\n", dq_y2_ac); |
| printf(" DQ UV DC: %d\n", dq_uv_dc); |
| printf(" DQ UV AC: %d\n", dq_uv_ac); |
| } |
| if ((*bit_pos >> 3) >= partition0_length) { |
| LOG_ERROR("Truncated lossy bitstream."); |
| return WEBP_INFO_TRUNCATED_DATA; |
| } |
| return WEBP_INFO_OK; |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // Lossless bitstream analysis. |
| |
| static int LLGetBits(const uint8_t* const data, size_t data_size, size_t nb, |
| int* val, uint64_t* const bit_pos) { |
| uint32_t i = 0; |
| *val = 0; |
| while (i < nb) { |
| const uint64_t p = (*bit_pos)++; |
| if ((p >> 3) >= data_size) { |
| return 0; |
| } else { |
| const int bit = !!(data[p >> 3] & (1 << ((p & 7)))); |
| *val = *val | (bit << i); |
| ++i; |
| } |
| } |
| return 1; |
| } |
| |
| #define LL_GET_BITS(v, n) \ |
| do { \ |
| if (!LLGetBits(data, data_size, n, &(v), bit_pos)) { \ |
| LOG_ERROR("Truncated lossless bitstream."); \ |
| return WEBP_INFO_TRUNCATED_DATA; \ |
| } \ |
| } while (0) |
| |
| static WebPInfoStatus ParseLosslessTransform(WebPInfo* const webp_info, |
| const uint8_t* const data, |
| size_t data_size, |
| uint64_t* const bit_pos) { |
| int use_transform, block_size, n_colors; |
| LL_GET_BITS(use_transform, 1); |
| printf(" Use transform: %s\n", use_transform ? "Yes" : "No"); |
| if (use_transform) { |
| int type; |
| LL_GET_BITS(type, 2); |
| printf(" 1st transform: %s (%d)\n", kLosslessTransforms[type], type); |
| switch (type) { |
| case PREDICTOR_TRANSFORM: |
| case CROSS_COLOR_TRANSFORM: |
| LL_GET_BITS(block_size, 3); |
| block_size = 1 << (block_size + 2); |
| printf(" Tran. block size: %d\n", block_size); |
| break; |
| case COLOR_INDEXING_TRANSFORM: |
| LL_GET_BITS(n_colors, 8); |
| n_colors += 1; |
| printf(" No. of colors: %d\n", n_colors); |
| break; |
| default: break; |
| } |
| } |
| return WEBP_INFO_OK; |
| } |
| |
| static WebPInfoStatus ParseLosslessHeader(const ChunkData* const chunk_data, |
| WebPInfo* const webp_info) { |
| const uint8_t* data = chunk_data->payload_; |
| size_t data_size = chunk_data->size_ - CHUNK_HEADER_SIZE; |
| uint64_t bit_position = 0; |
| uint64_t* const bit_pos = &bit_position; |
| WebPInfoStatus status; |
| printf(" Parsing lossless bitstream...\n"); |
| if (data_size < VP8L_FRAME_HEADER_SIZE) { |
| LOG_ERROR("Truncated lossless bitstream."); |
| return WEBP_INFO_TRUNCATED_DATA; |
| } |
| if (data[0] != VP8L_MAGIC_BYTE) { |
| LOG_ERROR("Invalid lossless bitstream signature."); |
| return WEBP_INFO_BITSTREAM_ERROR; |
| } |
| data += 1; |
| data_size -= 1; |
| { |
| int width, height, has_alpha, version; |
| LL_GET_BITS(width, 14); |
| LL_GET_BITS(height, 14); |
| LL_GET_BITS(has_alpha, 1); |
| LL_GET_BITS(version, 3); |
| width += 1; |
| height += 1; |
| printf(" Width: %d\n", width); |
| printf(" Height: %d\n", height); |
| printf(" Alpha: %d\n", has_alpha); |
| printf(" Version: %d\n", version); |
| } |
| status = ParseLosslessTransform(webp_info, data, data_size, bit_pos); |
| if (status != WEBP_INFO_OK) return status; |
| return WEBP_INFO_OK; |
| } |
| |
| static WebPInfoStatus ParseAlphaHeader(const ChunkData* const chunk_data, |
| WebPInfo* const webp_info) { |
| const uint8_t* data = chunk_data->payload_; |
| size_t data_size = chunk_data->size_ - CHUNK_HEADER_SIZE; |
| if (data_size <= ALPHA_HEADER_LEN) { |
| LOG_ERROR("Truncated ALPH chunk."); |
| return WEBP_INFO_TRUNCATED_DATA; |
| } |
| printf(" Parsing ALPH chunk...\n"); |
| { |
| const int compression_method = (data[0] >> 0) & 0x03; |
| const int filter = (data[0] >> 2) & 0x03; |
| const int pre_processing = (data[0] >> 4) & 0x03; |
| const int reserved_bits = (data[0] >> 6) & 0x03; |
| printf(" Compression: %d\n", compression_method); |
| printf(" Filter: %s (%d)\n", |
| kAlphaFilterMethods[filter], filter); |
| printf(" Pre-processing: %d\n", pre_processing); |
| if (compression_method > ALPHA_LOSSLESS_COMPRESSION) { |
| LOG_ERROR("Invalid Alpha compression method."); |
| return WEBP_INFO_BITSTREAM_ERROR; |
| } |
| if (pre_processing > ALPHA_PREPROCESSED_LEVELS) { |
| LOG_ERROR("Invalid Alpha pre-processing method."); |
| return WEBP_INFO_BITSTREAM_ERROR; |
| } |
| if (reserved_bits != 0) { |
| LOG_WARN("Reserved bits in ALPH chunk header are not all 0."); |
| } |
| data += ALPHA_HEADER_LEN; |
| data_size -= ALPHA_HEADER_LEN; |
| if (compression_method == ALPHA_LOSSLESS_COMPRESSION) { |
| uint64_t bit_pos = 0; |
| WebPInfoStatus status = |
| ParseLosslessTransform(webp_info, data, data_size, &bit_pos); |
| if (status != WEBP_INFO_OK) return status; |
| } |
| } |
| return WEBP_INFO_OK; |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // Chunk parsing. |
| |
| static WebPInfoStatus ParseRIFFHeader(const WebPInfo* const webp_info, |
| MemBuffer* const mem) { |
| const size_t min_size = RIFF_HEADER_SIZE + CHUNK_HEADER_SIZE; |
| size_t riff_size; |
| |
| if (MemDataSize(mem) < min_size) { |
| LOG_ERROR("Truncated data detected when parsing RIFF header."); |
| return WEBP_INFO_TRUNCATED_DATA; |
| } |
| if (memcmp(GetBuffer(mem), "RIFF", CHUNK_SIZE_BYTES) || |
| memcmp(GetBuffer(mem) + CHUNK_HEADER_SIZE, "WEBP", CHUNK_SIZE_BYTES)) { |
| LOG_ERROR("Corrupted RIFF header."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| riff_size = GetLE32(GetBuffer(mem) + TAG_SIZE); |
| if (riff_size < CHUNK_HEADER_SIZE) { |
| LOG_ERROR("RIFF size is too small."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| if (riff_size > MAX_CHUNK_PAYLOAD) { |
| LOG_ERROR("RIFF size is over limit."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| riff_size += CHUNK_HEADER_SIZE; |
| if (!webp_info->quiet_) { |
| printf("RIFF HEADER:\n"); |
| printf(" File size: %6d\n", (int)riff_size); |
| } |
| if (riff_size < mem->end_) { |
| LOG_WARN("RIFF size is smaller than the file size."); |
| mem->end_ = riff_size; |
| } else if (riff_size > mem->end_) { |
| LOG_ERROR("Truncated data detected when parsing RIFF payload."); |
| return WEBP_INFO_TRUNCATED_DATA; |
| } |
| Skip(mem, RIFF_HEADER_SIZE); |
| return WEBP_INFO_OK; |
| } |
| |
| static WebPInfoStatus ParseChunk(const WebPInfo* const webp_info, |
| MemBuffer* const mem, |
| ChunkData* const chunk_data) { |
| memset(chunk_data, 0, sizeof(*chunk_data)); |
| if (MemDataSize(mem) < CHUNK_HEADER_SIZE) { |
| LOG_ERROR("Truncated data detected when parsing chunk header."); |
| return WEBP_INFO_TRUNCATED_DATA; |
| } else { |
| const size_t chunk_start_offset = mem->start_; |
| const uint32_t fourcc = ReadMemBufLE32(mem); |
| const uint32_t payload_size = ReadMemBufLE32(mem); |
| const uint32_t payload_size_padded = payload_size + (payload_size & 1); |
| const size_t chunk_size = CHUNK_HEADER_SIZE + payload_size_padded; |
| int i; |
| if (payload_size > MAX_CHUNK_PAYLOAD) { |
| LOG_ERROR("Size of chunk payload is over limit."); |
| return WEBP_INFO_INVALID_PARAM; |
| } |
| if (payload_size_padded > MemDataSize(mem)){ |
| LOG_ERROR("Truncated data detected when parsing chunk payload."); |
| return WEBP_INFO_TRUNCATED_DATA; |
| } |
| for (i = 0; i < CHUNK_TYPES; ++i) { |
| if (!memcmp(kWebPChunkTags[i], &fourcc, TAG_SIZE)) break; |
| } |
| chunk_data->offset_ = chunk_start_offset; |
| chunk_data->size_ = chunk_size; |
| chunk_data->id_ = (ChunkID)i; |
| chunk_data->payload_ = GetBuffer(mem); |
| if (chunk_data->id_ == CHUNK_ANMF) { |
| if (payload_size != payload_size_padded) { |
| LOG_ERROR("ANMF chunk size should always be even."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| // There are sub-chunks to be parsed in an ANMF chunk. |
| Skip(mem, ANMF_CHUNK_SIZE); |
| } else { |
| Skip(mem, payload_size_padded); |
| } |
| return WEBP_INFO_OK; |
| } |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // Chunk analysis. |
| |
| static WebPInfoStatus ProcessVP8XChunk(const ChunkData* const chunk_data, |
| WebPInfo* const webp_info) { |
| const uint8_t* data = chunk_data->payload_; |
| if (webp_info->chunk_counts_[CHUNK_VP8] || |
| webp_info->chunk_counts_[CHUNK_VP8L] || |
| webp_info->chunk_counts_[CHUNK_VP8X]) { |
| LOG_ERROR("Already seen a VP8/VP8L/VP8X chunk when parsing VP8X chunk."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| if (chunk_data->size_ != VP8X_CHUNK_SIZE + CHUNK_HEADER_SIZE) { |
| LOG_ERROR("Corrupted VP8X chunk."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| ++webp_info->chunk_counts_[CHUNK_VP8X]; |
| webp_info->feature_flags_ = *data; |
| data += 4; |
| webp_info->canvas_width_ = 1 + ReadLE24(&data); |
| webp_info->canvas_height_ = 1 + ReadLE24(&data); |
| if (!webp_info->quiet_) { |
| printf(" ICCP: %d\n Alpha: %d\n EXIF: %d\n XMP: %d\n Animation: %d\n", |
| (webp_info->feature_flags_ & ICCP_FLAG) != 0, |
| (webp_info->feature_flags_ & ALPHA_FLAG) != 0, |
| (webp_info->feature_flags_ & EXIF_FLAG) != 0, |
| (webp_info->feature_flags_ & XMP_FLAG) != 0, |
| (webp_info->feature_flags_ & ANIMATION_FLAG) != 0); |
| printf(" Canvas size %d x %d\n", |
| webp_info->canvas_width_, webp_info->canvas_height_); |
| } |
| if (webp_info->canvas_width_ > MAX_CANVAS_SIZE) { |
| LOG_WARN("Canvas width is out of range in VP8X chunk."); |
| } |
| if (webp_info->canvas_height_ > MAX_CANVAS_SIZE) { |
| LOG_WARN("Canvas height is out of range in VP8X chunk."); |
| } |
| if ((uint64_t)webp_info->canvas_width_ * webp_info->canvas_height_ > |
| MAX_IMAGE_AREA) { |
| LOG_WARN("Canvas area is out of range in VP8X chunk."); |
| } |
| return WEBP_INFO_OK; |
| } |
| |
| static WebPInfoStatus ProcessANIMChunk(const ChunkData* const chunk_data, |
| WebPInfo* const webp_info) { |
| const uint8_t* data = chunk_data->payload_; |
| if (!webp_info->chunk_counts_[CHUNK_VP8X]) { |
| LOG_ERROR("ANIM chunk detected before VP8X chunk."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| if (chunk_data->size_ != ANIM_CHUNK_SIZE + CHUNK_HEADER_SIZE) { |
| LOG_ERROR("Corrupted ANIM chunk."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| webp_info->bgcolor_ = ReadLE32(&data); |
| webp_info->loop_count_ = ReadLE16(&data); |
| ++webp_info->chunk_counts_[CHUNK_ANIM]; |
| if (!webp_info->quiet_) { |
| printf(" Background color:(ARGB) %02x %02x %02x %02x\n", |
| (webp_info->bgcolor_ >> 24) & 0xff, |
| (webp_info->bgcolor_ >> 16) & 0xff, |
| (webp_info->bgcolor_ >> 8) & 0xff, |
| webp_info->bgcolor_ & 0xff); |
| printf(" Loop count : %d\n", webp_info->loop_count_); |
| } |
| if (webp_info->loop_count_ > MAX_LOOP_COUNT) { |
| LOG_WARN("Loop count is out of range in ANIM chunk."); |
| } |
| return WEBP_INFO_OK; |
| } |
| |
| static WebPInfoStatus ProcessANMFChunk(const ChunkData* const chunk_data, |
| WebPInfo* const webp_info) { |
| const uint8_t* data = chunk_data->payload_; |
| int offset_x, offset_y, width, height, duration, blend, dispose, temp; |
| if (webp_info->is_processing_anim_frame_) { |
| LOG_ERROR("ANMF chunk detected within another ANMF chunk."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| if (!webp_info->chunk_counts_[CHUNK_ANIM]) { |
| LOG_ERROR("ANMF chunk detected before ANIM chunk."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| if (chunk_data->size_ <= CHUNK_HEADER_SIZE + ANMF_CHUNK_SIZE) { |
| LOG_ERROR("Truncated data detected when parsing ANMF chunk."); |
| return WEBP_INFO_TRUNCATED_DATA; |
| } |
| offset_x = 2 * ReadLE24(&data); |
| offset_y = 2 * ReadLE24(&data); |
| width = 1 + ReadLE24(&data); |
| height = 1 + ReadLE24(&data); |
| duration = ReadLE24(&data); |
| temp = *data; |
| dispose = temp & 1; |
| blend = (temp >> 1) & 1; |
| ++webp_info->chunk_counts_[CHUNK_ANMF]; |
| if (!webp_info->quiet_) { |
| printf(" Offset_X: %d\n Offset_Y: %d\n Width: %d\n Height: %d\n" |
| " Duration: %d\n Dispose: %d\n Blend: %d\n", |
| offset_x, offset_y, width, height, duration, dispose, blend); |
| } |
| if (duration > MAX_DURATION) { |
| LOG_ERROR("Invalid duration parameter in ANMF chunk."); |
| return WEBP_INFO_INVALID_PARAM; |
| } |
| if (offset_x > MAX_POSITION_OFFSET || offset_y > MAX_POSITION_OFFSET) { |
| LOG_ERROR("Invalid offset parameters in ANMF chunk."); |
| return WEBP_INFO_INVALID_PARAM; |
| } |
| if ((uint64_t)offset_x + width > (uint64_t)webp_info->canvas_width_ || |
| (uint64_t)offset_y + height > (uint64_t)webp_info->canvas_height_) { |
| LOG_ERROR("Frame exceeds canvas in ANMF chunk."); |
| return WEBP_INFO_INVALID_PARAM; |
| } |
| webp_info->is_processing_anim_frame_ = 1; |
| webp_info->seen_alpha_subchunk_ = 0; |
| webp_info->seen_image_subchunk_ = 0; |
| webp_info->frame_width_ = width; |
| webp_info->frame_height_ = height; |
| webp_info->anim_frame_data_size_ = |
| chunk_data->size_ - CHUNK_HEADER_SIZE - ANMF_CHUNK_SIZE; |
| return WEBP_INFO_OK; |
| } |
| |
| static WebPInfoStatus ProcessImageChunk(const ChunkData* const chunk_data, |
| WebPInfo* const webp_info) { |
| const uint8_t* data = chunk_data->payload_ - CHUNK_HEADER_SIZE; |
| WebPBitstreamFeatures features; |
| const VP8StatusCode vp8_status = |
| WebPGetFeatures(data, chunk_data->size_, &features); |
| if (vp8_status != VP8_STATUS_OK) { |
| LOG_ERROR("VP8/VP8L bitstream error."); |
| return WEBP_INFO_BITSTREAM_ERROR; |
| } |
| if (!webp_info->quiet_) { |
| assert(features.format >= 0 && features.format <= 2); |
| printf(" Width: %d\n Height: %d\n Alpha: %d\n Animation: %d\n" |
| " Format: %s (%d)\n", |
| features.width, features.height, features.has_alpha, |
| features.has_animation, kFormats[features.format], features.format); |
| } |
| if (webp_info->is_processing_anim_frame_) { |
| ++webp_info->anmf_subchunk_counts_[chunk_data->id_ == CHUNK_VP8 ? 0 : 1]; |
| if (chunk_data->id_ == CHUNK_VP8L && webp_info->seen_alpha_subchunk_) { |
| LOG_ERROR("Both VP8L and ALPH sub-chunks are present in an ANMF chunk."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| if (webp_info->frame_width_ != features.width || |
| webp_info->frame_height_ != features.height) { |
| LOG_ERROR("Frame size in VP8/VP8L sub-chunk differs from ANMF header."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| if (webp_info->seen_image_subchunk_) { |
| LOG_ERROR("Consecutive VP8/VP8L sub-chunks in an ANMF chunk."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| webp_info->seen_image_subchunk_ = 1; |
| } else { |
| if (webp_info->chunk_counts_[CHUNK_VP8] || |
| webp_info->chunk_counts_[CHUNK_VP8L]) { |
| LOG_ERROR("Multiple VP8/VP8L chunks detected."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| if (chunk_data->id_ == CHUNK_VP8L && |
| webp_info->chunk_counts_[CHUNK_ALPHA]) { |
| LOG_WARN("Both VP8L and ALPH chunks are detected."); |
| } |
| if (webp_info->chunk_counts_[CHUNK_ANIM] || |
| webp_info->chunk_counts_[CHUNK_ANMF]) { |
| LOG_ERROR("VP8/VP8L chunk and ANIM/ANMF chunk are both detected."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| if (webp_info->chunk_counts_[CHUNK_VP8X]) { |
| if (webp_info->canvas_width_ != features.width || |
| webp_info->canvas_height_ != features.height) { |
| LOG_ERROR("Image size in VP8/VP8L chunk differs from VP8X chunk."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| } else { |
| webp_info->canvas_width_ = features.width; |
| webp_info->canvas_height_ = features.height; |
| if (webp_info->canvas_width_ < 1 || webp_info->canvas_height_ < 1 || |
| webp_info->canvas_width_ > MAX_CANVAS_SIZE || |
| webp_info->canvas_height_ > MAX_CANVAS_SIZE || |
| (uint64_t)webp_info->canvas_width_ * webp_info->canvas_height_ > |
| MAX_IMAGE_AREA) { |
| LOG_WARN("Invalid parameters in VP8/VP8L chunk."); |
| } |
| } |
| ++webp_info->chunk_counts_[chunk_data->id_]; |
| } |
| ++webp_info->num_frames_; |
| webp_info->has_alpha_ |= features.has_alpha; |
| if (webp_info->parse_bitstream_) { |
| const int is_lossy = (chunk_data->id_ == CHUNK_VP8); |
| const WebPInfoStatus status = |
| is_lossy ? ParseLossyHeader(chunk_data, webp_info) |
| : ParseLosslessHeader(chunk_data, webp_info); |
| if (status != WEBP_INFO_OK) return status; |
| } |
| return WEBP_INFO_OK; |
| } |
| |
| static WebPInfoStatus ProcessALPHChunk(const ChunkData* const chunk_data, |
| WebPInfo* const webp_info) { |
| if (webp_info->is_processing_anim_frame_) { |
| ++webp_info->anmf_subchunk_counts_[2]; |
| if (webp_info->seen_alpha_subchunk_) { |
| LOG_ERROR("Consecutive ALPH sub-chunks in an ANMF chunk."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| webp_info->seen_alpha_subchunk_ = 1; |
| |
| if (webp_info->seen_image_subchunk_) { |
| LOG_ERROR("ALPHA sub-chunk detected after VP8 sub-chunk " |
| "in an ANMF chunk."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| } else { |
| if (webp_info->chunk_counts_[CHUNK_ANIM] || |
| webp_info->chunk_counts_[CHUNK_ANMF]) { |
| LOG_ERROR("ALPHA chunk and ANIM/ANMF chunk are both detected."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| if (!webp_info->chunk_counts_[CHUNK_VP8X]) { |
| LOG_ERROR("ALPHA chunk detected before VP8X chunk."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| if (webp_info->chunk_counts_[CHUNK_VP8]) { |
| LOG_ERROR("ALPHA chunk detected after VP8 chunk."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| if (webp_info->chunk_counts_[CHUNK_ALPHA]) { |
| LOG_ERROR("Multiple ALPHA chunks detected."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| ++webp_info->chunk_counts_[CHUNK_ALPHA]; |
| } |
| webp_info->has_alpha_ = 1; |
| if (webp_info->parse_bitstream_) { |
| const WebPInfoStatus status = ParseAlphaHeader(chunk_data, webp_info); |
| if (status != WEBP_INFO_OK) return status; |
| } |
| return WEBP_INFO_OK; |
| } |
| |
| static WebPInfoStatus ProcessICCPChunk(const ChunkData* const chunk_data, |
| WebPInfo* const webp_info) { |
| (void)chunk_data; |
| if (!webp_info->chunk_counts_[CHUNK_VP8X]) { |
| LOG_ERROR("ICCP chunk detected before VP8X chunk."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| if (webp_info->chunk_counts_[CHUNK_VP8] || |
| webp_info->chunk_counts_[CHUNK_VP8L] || |
| webp_info->chunk_counts_[CHUNK_ANIM]) { |
| LOG_ERROR("ICCP chunk detected after image data."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| ++webp_info->chunk_counts_[CHUNK_ICCP]; |
| return WEBP_INFO_OK; |
| } |
| |
| static WebPInfoStatus ProcessChunk(const ChunkData* const chunk_data, |
| WebPInfo* const webp_info) { |
| WebPInfoStatus status = WEBP_INFO_OK; |
| ChunkID id = chunk_data->id_; |
| if (chunk_data->id_ == CHUNK_UNKNOWN) { |
| char error_message[50]; |
| snprintf(error_message, 50, "Unknown chunk at offset %6d, length %6d", |
| (int)chunk_data->offset_, (int)chunk_data->size_); |
| LOG_WARN(error_message); |
| } else { |
| if (!webp_info->quiet_) { |
| const char* tag = kWebPChunkTags[chunk_data->id_]; |
| printf("Chunk %c%c%c%c at offset %6d, length %6d\n", |
| tag[0], tag[1], tag[2], tag[3], (int)chunk_data->offset_, |
| (int)chunk_data->size_); |
| } |
| } |
| switch (id) { |
| case CHUNK_VP8: |
| case CHUNK_VP8L: |
| status = ProcessImageChunk(chunk_data, webp_info); |
| break; |
| case CHUNK_VP8X: |
| status = ProcessVP8XChunk(chunk_data, webp_info); |
| break; |
| case CHUNK_ALPHA: |
| status = ProcessALPHChunk(chunk_data, webp_info); |
| break; |
| case CHUNK_ANIM: |
| status = ProcessANIMChunk(chunk_data, webp_info); |
| break; |
| case CHUNK_ANMF: |
| status = ProcessANMFChunk(chunk_data, webp_info); |
| break; |
| case CHUNK_ICCP: |
| status = ProcessICCPChunk(chunk_data, webp_info); |
| break; |
| case CHUNK_EXIF: |
| case CHUNK_XMP: |
| ++webp_info->chunk_counts_[id]; |
| break; |
| case CHUNK_UNKNOWN: |
| default: |
| break; |
| } |
| if (webp_info->is_processing_anim_frame_ && id != CHUNK_ANMF) { |
| if (webp_info->anim_frame_data_size_ == chunk_data->size_) { |
| if (!webp_info->seen_image_subchunk_) { |
| LOG_ERROR("No VP8/VP8L chunk detected in an ANMF chunk."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| webp_info->is_processing_anim_frame_ = 0; |
| } else if (webp_info->anim_frame_data_size_ > chunk_data->size_) { |
| webp_info->anim_frame_data_size_ -= chunk_data->size_; |
| } else { |
| LOG_ERROR("Truncated data detected when parsing ANMF chunk."); |
| return WEBP_INFO_TRUNCATED_DATA; |
| } |
| } |
| return status; |
| } |
| |
| static WebPInfoStatus Validate(const WebPInfo* const webp_info) { |
| if (webp_info->num_frames_ < 1) { |
| LOG_ERROR("No image/frame detected."); |
| return WEBP_INFO_MISSING_DATA; |
| } |
| if (webp_info->chunk_counts_[CHUNK_VP8X]) { |
| const int iccp = !!(webp_info->feature_flags_ & ICCP_FLAG); |
| const int exif = !!(webp_info->feature_flags_ & EXIF_FLAG); |
| const int xmp = !!(webp_info->feature_flags_ & XMP_FLAG); |
| const int animation = !!(webp_info->feature_flags_ & ANIMATION_FLAG); |
| const int alpha = !!(webp_info->feature_flags_ & ALPHA_FLAG); |
| if (!alpha && webp_info->has_alpha_) { |
| LOG_ERROR("Unexpected alpha data detected."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| if (alpha && !webp_info->has_alpha_) { |
| LOG_WARN("Alpha flag is set with no alpha data present."); |
| } |
| if (iccp && !webp_info->chunk_counts_[CHUNK_ICCP]) { |
| LOG_ERROR("Missing ICCP chunk."); |
| return WEBP_INFO_MISSING_DATA; |
| } |
| if (exif && !webp_info->chunk_counts_[CHUNK_EXIF]) { |
| LOG_ERROR("Missing EXIF chunk."); |
| return WEBP_INFO_MISSING_DATA; |
| } |
| if (xmp && !webp_info->chunk_counts_[CHUNK_XMP]) { |
| LOG_ERROR("Missing XMP chunk."); |
| return WEBP_INFO_MISSING_DATA; |
| } |
| if (!iccp && webp_info->chunk_counts_[CHUNK_ICCP]) { |
| LOG_ERROR("Unexpected ICCP chunk detected."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| if (!exif && webp_info->chunk_counts_[CHUNK_EXIF]) { |
| LOG_ERROR("Unexpected EXIF chunk detected."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| if (!xmp && webp_info->chunk_counts_[CHUNK_XMP]) { |
| LOG_ERROR("Unexpected XMP chunk detected."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| // Incomplete animation frame. |
| if (webp_info->is_processing_anim_frame_) return WEBP_INFO_MISSING_DATA; |
| if (!animation && webp_info->num_frames_ > 1) { |
| LOG_ERROR("More than 1 frame detected in non-animation file."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| if (animation && (!webp_info->chunk_counts_[CHUNK_ANIM] || |
| !webp_info->chunk_counts_[CHUNK_ANMF])) { |
| LOG_ERROR("No ANIM/ANMF chunk detected in animation file."); |
| return WEBP_INFO_PARSE_ERROR; |
| } |
| } |
| return WEBP_INFO_OK; |
| } |
| |
| static void ShowSummary(const WebPInfo* const webp_info) { |
| int i; |
| printf("Summary:\n"); |
| printf("Number of frames: %d\n", webp_info->num_frames_); |
| printf("Chunk type : VP8 VP8L VP8X ALPH ANIM ANMF(VP8 /VP8L/ALPH) ICCP " |
| "EXIF XMP\n"); |
| printf("Chunk counts: "); |
| for (i = 0; i < CHUNK_TYPES; ++i) { |
| printf("%4d ", webp_info->chunk_counts_[i]); |
| if (i == CHUNK_ANMF) { |
| printf("%4d %4d %4d ", |
| webp_info->anmf_subchunk_counts_[0], |
| webp_info->anmf_subchunk_counts_[1], |
| webp_info->anmf_subchunk_counts_[2]); |
| } |
| } |
| printf("\n"); |
| } |
| |
| static WebPInfoStatus AnalyzeWebP(WebPInfo* const webp_info, |
| const WebPData* webp_data) { |
| ChunkData chunk_data; |
| MemBuffer mem_buffer; |
| WebPInfoStatus webp_info_status = WEBP_INFO_OK; |
| |
| InitMemBuffer(&mem_buffer, webp_data); |
| webp_info_status = ParseRIFFHeader(webp_info, &mem_buffer); |
| if (webp_info_status != WEBP_INFO_OK) goto Error; |
| |
| // Loop through all the chunks. Terminate immediately in case of error. |
| while (webp_info_status == WEBP_INFO_OK && MemDataSize(&mem_buffer) > 0) { |
| webp_info_status = ParseChunk(webp_info, &mem_buffer, &chunk_data); |
| if (webp_info_status != WEBP_INFO_OK) goto Error; |
| webp_info_status = ProcessChunk(&chunk_data, webp_info); |
| } |
| if (webp_info_status != WEBP_INFO_OK) goto Error; |
| if (webp_info->show_summary_) ShowSummary(webp_info); |
| |
| // Final check. |
| webp_info_status = Validate(webp_info); |
| |
| Error: |
| if (!webp_info->quiet_) { |
| if (webp_info_status == WEBP_INFO_OK) { |
| printf("No error detected.\n"); |
| } else { |
| printf("Errors detected.\n"); |
| } |
| } |
| return webp_info_status; |
| } |
| |
| static void HelpShort(void) { |
| printf("Usage: webpinfo [options] in_files\n" |
| "Try -longhelp for an exhaustive list of options.\n"); |
| } |
| |
| static void HelpLong(void) { |
| printf("Usage: webpinfo [options] in_files\n" |
| "Note: there could be multiple input files;\n" |
| " options must come before input files.\n" |
| "Options:\n" |
| " -version ........... Print version number and exit.\n" |
| " -quiet ............. Do not show chunk parsing information.\n" |
| " -diag .............. Show parsing error diagnosis.\n" |
| " -summary ........... Show chunk stats summary.\n" |
| " -bitstream_info .... Parse bitstream header.\n"); |
| } |
| |
| int main(int argc, const char* argv[]) { |
| int c, quiet = 0, show_diag = 0, show_summary = 0; |
| int parse_bitstream = 0; |
| WebPInfoStatus webp_info_status = WEBP_INFO_OK; |
| WebPInfo webp_info; |
| |
| if (argc == 1) { |
| HelpShort(); |
| return WEBP_INFO_OK; |
| } |
| |
| // Parse command-line input. |
| for (c = 1; c < argc; ++c) { |
| if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) { |
| HelpShort(); |
| return WEBP_INFO_OK; |
| } else if (!strcmp(argv[c], "-H") || !strcmp(argv[c], "-longhelp")) { |
| HelpLong(); |
| return WEBP_INFO_OK; |
| } else if (!strcmp(argv[c], "-quiet")) { |
| quiet = 1; |
| } else if (!strcmp(argv[c], "-diag")) { |
| show_diag = 1; |
| } else if (!strcmp(argv[c], "-summary")) { |
| show_summary = 1; |
| } else if (!strcmp(argv[c], "-bitstream_info")) { |
| parse_bitstream = 1; |
| } else if (!strcmp(argv[c], "-version")) { |
| const int version = WebPGetDecoderVersion(); |
| printf("WebP Decoder version: %d.%d.%d\n", |
| (version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff); |
| return 0; |
| } else { // Assume the remaining are all input files. |
| break; |
| } |
| } |
| |
| if (c == argc) { |
| HelpShort(); |
| return WEBP_INFO_INVALID_COMMAND; |
| } |
| |
| // Process input files one by one. |
| for (; c < argc; ++c) { |
| WebPData webp_data; |
| const char* in_file = NULL; |
| WebPInfoInit(&webp_info); |
| webp_info.quiet_ = quiet; |
| webp_info.show_diagnosis_ = show_diag; |
| webp_info.show_summary_ = show_summary; |
| webp_info.parse_bitstream_ = parse_bitstream; |
| in_file = argv[c]; |
| if (in_file == NULL || !ReadFileToWebPData(in_file, &webp_data)) { |
| webp_info_status = WEBP_INFO_INVALID_COMMAND; |
| fprintf(stderr, "Failed to open input file %s.\n", in_file); |
| continue; |
| } |
| if (!webp_info.quiet_) printf("File: %s\n", in_file); |
| webp_info_status = AnalyzeWebP(&webp_info, &webp_data); |
| WebPDataClear(&webp_data); |
| } |
| return webp_info_status; |
| } |