Coverage Report

Created: 2026-06-10 06:43

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