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