Coverage Report

Created: 2025-07-11 06:36

/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
}