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 }