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