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