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