xref: /aosp_15_r20/external/webp/examples/webpinfo.c (revision b2055c353e87c8814eb2b6b1b11112a1562253bd)
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