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