Coverage Report

Created: 2026-06-07 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libwebp/examples/anim_util.c
Line
Count
Source
1
// Copyright 2015 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
// Utilities for animated images
11
12
#include "./anim_util.h"
13
14
#include <assert.h>
15
#include <math.h>
16
#include <stdio.h>
17
#include <string.h>
18
19
#if defined(WEBP_HAVE_GIF)
20
#include <gif_lib.h>
21
#endif
22
23
#include "../imageio/imageio_util.h"
24
#include "./gifdec.h"
25
#include "./unicode.h"
26
#include "webp/decode.h"
27
#include "webp/demux.h"
28
#include "webp/format_constants.h"
29
#include "webp/mux_types.h"
30
#include "webp/types.h"
31
32
#if defined(_MSC_VER) && _MSC_VER < 1900
33
#define snprintf _snprintf
34
#endif
35
36
static const int kNumChannels = 4;
37
38
// -----------------------------------------------------------------------------
39
// Common utilities.
40
41
#if defined(WEBP_HAVE_GIF)
42
// Returns true if the frame covers the full canvas.
43
static int IsFullFrame(int width, int height, int canvas_width,
44
0
                       int canvas_height) {
45
0
  return (width == canvas_width && height == canvas_height);
46
0
}
47
#endif  // WEBP_HAVE_GIF
48
49
11.5k
static int CheckSizeForOverflow(uint64_t size) {
50
11.5k
  return (size == (size_t)size);
51
11.5k
}
52
53
5.39k
static int AllocateFrames(AnimatedImage* const image, uint32_t num_frames) {
54
5.39k
  uint32_t i;
55
5.39k
  uint8_t* mem = NULL;
56
5.39k
  DecodedFrame* frames = NULL;
57
5.39k
  const uint64_t rgba_size =
58
5.39k
      (uint64_t)image->canvas_width * kNumChannels * image->canvas_height;
59
5.39k
  const uint64_t total_size = (uint64_t)num_frames * rgba_size * sizeof(*mem);
60
5.39k
  const uint64_t total_frame_size = (uint64_t)num_frames * sizeof(*frames);
61
5.39k
  if (!CheckSizeForOverflow(total_size) ||
62
5.39k
      !CheckSizeForOverflow(total_frame_size)) {
63
0
    return 0;
64
0
  }
65
5.39k
  mem = (uint8_t*)WebPMalloc((size_t)total_size);
66
5.39k
  frames = (DecodedFrame*)WebPMalloc((size_t)total_frame_size);
67
68
5.39k
  if (mem == NULL || frames == NULL) {
69
29
    WebPFree(mem);
70
29
    WebPFree(frames);
71
29
    return 0;
72
29
  }
73
5.36k
  WebPFree(image->raw_mem);
74
5.36k
  image->num_frames = num_frames;
75
5.36k
  image->frames = frames;
76
10.7k
  for (i = 0; i < num_frames; ++i) {
77
5.36k
    frames[i].rgba = mem + i * rgba_size;
78
5.36k
    frames[i].duration = 0;
79
5.36k
    frames[i].is_key_frame = 0;
80
5.36k
  }
81
5.36k
  image->raw_mem = mem;
82
5.36k
  return 1;
83
5.39k
}
84
85
7.43k
void ClearAnimatedImage(AnimatedImage* const image) {
86
7.43k
  if (image != NULL) {
87
7.43k
    WebPFree(image->raw_mem);
88
7.43k
    WebPFree(image->frames);
89
7.43k
    image->num_frames = 0;
90
7.43k
    image->frames = NULL;
91
7.43k
    image->raw_mem = NULL;
92
7.43k
  }
93
7.43k
}
94
95
WEBP_NODISCARD
96
807
int CheckMultiplicationOverflow(uint32_t val1, uint32_t val2, size_t* product) {
97
807
  const uint64_t size = (uint64_t)val1 * val2;
98
807
  if (CheckSizeForOverflow(size)) {
99
807
    *product = (size_t)size;
100
807
    return 1;
101
807
  }
102
0
  return 0;
103
807
}
104
105
WEBP_NODISCARD
106
0
int CheckAdditionOverflow(size_t val1, uint32_t val2, size_t* addition) {
107
0
  const uint64_t size = (uint64_t)val1 + val2;
108
0
  if (CheckSizeForOverflow(size)) {
109
0
    *addition = (size_t)size;
110
0
    return 1;
111
0
  }
112
0
  return 0;
113
0
}
114
115
#if defined(WEBP_HAVE_GIF)
116
117
// For the GIF functions below, the width, height, x_offset, y_offset fit on 16
118
// bits (but can fill the 16 bits) as per the GIF specification.
119
// Multiplications that can overflow are cast to 64 bits.
120
121
const uint32_t kGifDimMax = (1 << 16) - 1;
122
123
// Clear the canvas to transparent.
124
WEBP_NODISCARD
125
static int ZeroFillCanvas(uint8_t* rgba, uint32_t canvas_width,
126
0
                          uint32_t canvas_height) {
127
0
  size_t size;
128
0
  assert(canvas_width <= kGifDimMax && canvas_height <= kGifDimMax);
129
0
  if (!CheckMultiplicationOverflow(canvas_width * kNumChannels, canvas_height,
130
0
                                   &size)) {
131
0
    return 0;
132
0
  }
133
0
  memset(rgba, 0, size);
134
0
  return 1;
135
0
}
136
137
// Clear given frame rectangle to transparent.
138
WEBP_NODISCARD
139
static int ZeroFillFrameRect(uint8_t* rgba, uint32_t rgba_stride,
140
                             uint32_t x_offset, uint32_t y_offset,
141
0
                             uint32_t width, uint32_t height) {
142
0
  uint32_t j;
143
0
  size_t size, offset;
144
0
  assert(width <= kGifDimMax && x_offset <= kGifDimMax);
145
0
  assert(width * kNumChannels <= rgba_stride);
146
0
  if (!CheckMultiplicationOverflow(y_offset, rgba_stride, &size) ||
147
0
      !CheckAdditionOverflow(size, x_offset * kNumChannels, &offset)) {
148
0
    return 0;
149
0
  }
150
0
  rgba += offset;
151
0
  for (j = 0; j < height; ++j) {
152
0
    memset(rgba, 0, width * kNumChannels);
153
0
    rgba += rgba_stride;
154
0
  }
155
0
  return 1;
156
0
}
157
158
// Copy width * height pixels from 'src' to 'dst'.
159
WEBP_NODISCARD
160
static int CopyCanvas(const uint8_t* src, uint8_t* dst, uint32_t width,
161
0
                      uint32_t height) {
162
0
  size_t size;
163
0
  assert(width <= kGifDimMax && height <= kGifDimMax);
164
0
  if (!CheckMultiplicationOverflow(width * kNumChannels, height, &size)) {
165
0
    return 0;
166
0
  }
167
0
  assert(src != NULL && dst != NULL);
168
0
  memcpy(dst, src, size);
169
0
  return 1;
170
0
}
171
172
// Copy pixels in the given rectangle from 'src' to 'dst' honoring the 'stride'.
173
static int CopyFrameRectangle(const uint8_t* src, uint8_t* dst, uint32_t stride,
174
                              uint32_t x_offset, uint32_t y_offset,
175
0
                              uint32_t width, uint32_t height) {
176
0
  uint32_t j;
177
0
  const uint32_t width_in_bytes = width * kNumChannels;
178
0
  size_t offset, size;
179
0
  assert(width <= kGifDimMax && x_offset <= kGifDimMax);
180
0
  assert(width_in_bytes <= stride);
181
0
  if (!CheckMultiplicationOverflow(y_offset, stride, &size) ||
182
0
      !CheckAdditionOverflow(size, x_offset * kNumChannels, &offset)) {
183
0
    return 0;
184
0
  }
185
0
  src += offset;
186
0
  dst += offset;
187
0
  for (j = 0; j < height; ++j) {
188
0
    memcpy(dst, src, width_in_bytes);
189
0
    src += stride;
190
0
    dst += stride;
191
0
  }
192
0
  return 1;
193
0
}
194
#endif  // WEBP_HAVE_GIF
195
196
// Canonicalize all transparent pixels to transparent black to aid comparison.
197
static void CleanupTransparentPixels(uint32_t* rgba, uint32_t width,
198
807
                                     uint32_t height) {
199
807
  const uint32_t* const rgba_end = rgba + width * height;
200
2.37G
  while (rgba < rgba_end) {
201
2.37G
    const uint8_t alpha = (*rgba >> 24) & 0xff;
202
2.37G
    if (alpha == 0) {
203
801M
      *rgba = 0;
204
801M
    }
205
2.37G
    ++rgba;
206
2.37G
  }
207
807
}
208
209
// Dump frame to a PAM file. Returns true on success.
210
static int DumpFrame(const char filename[], const char dump_folder[],
211
                     uint32_t frame_num, const uint8_t rgba[], int canvas_width,
212
0
                     int canvas_height) {
213
0
  int ok = 0;
214
0
  size_t max_len;
215
0
  int y;
216
0
  const W_CHAR* base_name = NULL;
217
0
  W_CHAR* file_name = NULL;
218
0
  FILE* f = NULL;
219
0
  const char* row;
220
221
0
  if (dump_folder == NULL) dump_folder = (const char*)TO_W_CHAR(".");
222
223
0
  base_name = WSTRRCHR(filename, '/');
224
0
  base_name = (base_name == NULL) ? (const W_CHAR*)filename : base_name + 1;
225
0
  max_len = WSTRLEN(dump_folder) + 1 + WSTRLEN(base_name) + strlen("_frame_") +
226
0
            strlen(".pam") + 8;
227
0
  file_name = (W_CHAR*)WebPMalloc(max_len * sizeof(*file_name));
228
0
  if (file_name == NULL) goto End;
229
230
0
  if (WSNPRINTF(file_name, max_len, "%s/%s_frame_%d.pam",
231
0
                (const W_CHAR*)dump_folder, base_name, frame_num) < 0) {
232
0
    fprintf(stderr, "Error while generating file name\n");
233
0
    goto End;
234
0
  }
235
236
0
  f = WFOPEN(file_name, "wb");
237
0
  if (f == NULL) {
238
0
    WFPRINTF(stderr, "Error opening file for writing: %s\n", file_name);
239
0
    ok = 0;
240
0
    goto End;
241
0
  }
242
0
  if (fprintf(f,
243
0
              "P7\nWIDTH %d\nHEIGHT %d\n"
244
0
              "DEPTH 4\nMAXVAL 255\nTUPLTYPE RGB_ALPHA\nENDHDR\n",
245
0
              canvas_width, canvas_height) < 0) {
246
0
    WFPRINTF(stderr, "Write error for file %s\n", file_name);
247
0
    goto End;
248
0
  }
249
0
  row = (const char*)rgba;
250
0
  for (y = 0; y < canvas_height; ++y) {
251
0
    if (fwrite(row, canvas_width * kNumChannels, 1, f) != 1) {
252
0
      WFPRINTF(stderr, "Error writing to file: %s\n", file_name);
253
0
      goto End;
254
0
    }
255
0
    row += canvas_width * kNumChannels;
256
0
  }
257
0
  ok = 1;
258
0
End:
259
0
  if (f != NULL) fclose(f);
260
0
  WebPFree(file_name);
261
0
  return ok;
262
0
}
263
264
// -----------------------------------------------------------------------------
265
// WebP Decoding.
266
267
// Returns true if this is a valid WebP bitstream.
268
7.43k
static int IsWebP(const WebPData* const webp_data) {
269
7.43k
  return (WebPGetInfo(webp_data->bytes, webp_data->size, NULL, NULL) != 0);
270
7.43k
}
271
272
// Read animated WebP bitstream 'webp_data' into 'AnimatedImage' struct.
273
static int ReadAnimatedWebP(const char filename[],
274
                            const WebPData* const webp_data,
275
                            AnimatedImage* const image, int dump_frames,
276
6.84k
                            const char dump_folder[]) {
277
6.84k
  int ok = 0;
278
6.84k
  int dump_ok = 1;
279
6.84k
  uint32_t frame_index = 0;
280
6.84k
  int prev_frame_timestamp = 0;
281
6.84k
  WebPAnimDecoder* dec;
282
6.84k
  WebPAnimInfo anim_info;
283
284
6.84k
  memset(image, 0, sizeof(*image));
285
286
6.84k
  dec = WebPAnimDecoderNew(webp_data, NULL);
287
6.84k
  if (dec == NULL) {
288
1.45k
    WFPRINTF(stderr, "Error parsing image: %s\n", (const W_CHAR*)filename);
289
1.45k
    goto End;
290
1.45k
  }
291
292
5.39k
  if (!WebPAnimDecoderGetInfo(dec, &anim_info)) {
293
0
    fprintf(stderr, "Error getting global info about the animation\n");
294
0
    goto End;
295
0
  }
296
297
  // Animation properties.
298
5.39k
  image->canvas_width = anim_info.canvas_width;
299
5.39k
  image->canvas_height = anim_info.canvas_height;
300
5.39k
  image->loop_count = anim_info.loop_count;
301
5.39k
  image->bgcolor = anim_info.bgcolor;
302
303
  // Allocate frames.
304
5.39k
  if (!AllocateFrames(image, anim_info.frame_count)) goto End;
305
306
  // Decode frames.
307
6.16k
  while (WebPAnimDecoderHasMoreFrames(dec)) {
308
5.36k
    DecodedFrame* curr_frame;
309
5.36k
    uint8_t* curr_rgba;
310
5.36k
    uint8_t* frame_rgba;
311
5.36k
    int timestamp;
312
5.36k
    size_t size;
313
314
5.36k
    if (!WebPAnimDecoderGetNext(dec, &frame_rgba, &timestamp)) {
315
4.55k
      fprintf(stderr, "Error decoding frame #%u\n", frame_index);
316
4.55k
      goto End;
317
4.55k
    }
318
5.36k
    assert(frame_index < anim_info.frame_count);
319
807
    curr_frame = &image->frames[frame_index];
320
807
    curr_rgba = curr_frame->rgba;
321
807
    curr_frame->duration = timestamp - prev_frame_timestamp;
322
807
    curr_frame->is_key_frame = 0;  // Unused.
323
807
    if (!CheckMultiplicationOverflow(image->canvas_width * kNumChannels,
324
807
                                     image->canvas_height, &size)) {
325
0
      goto End;
326
0
    }
327
807
    memcpy(curr_rgba, frame_rgba, size);
328
329
    // Needed only because we may want to compare with GIF later.
330
807
    CleanupTransparentPixels((uint32_t*)curr_rgba, image->canvas_width,
331
807
                             image->canvas_height);
332
333
807
    if (dump_frames && dump_ok) {
334
0
      dump_ok = DumpFrame(filename, dump_folder, frame_index, curr_rgba,
335
0
                          image->canvas_width, image->canvas_height);
336
0
      if (!dump_ok) {  // Print error once, but continue decode loop.
337
0
        fprintf(stderr, "Error dumping frames to %s\n", dump_folder);
338
0
      }
339
0
    }
340
341
807
    ++frame_index;
342
807
    prev_frame_timestamp = timestamp;
343
807
  }
344
807
  ok = dump_ok;
345
807
  if (ok) image->format = ANIM_WEBP;
346
347
6.84k
End:
348
6.84k
  WebPAnimDecoderDelete(dec);
349
6.84k
  return ok;
350
807
}
351
352
// -----------------------------------------------------------------------------
353
// GIF Decoding.
354
355
#if defined(WEBP_HAVE_GIF)
356
357
typedef struct {
358
  const uint8_t* data;
359
  size_t size;
360
  size_t offset;
361
} GifBufferContext;
362
363
27.9k
static int MemoryReadGIF(GifFileType* gif, GifByteType* dest, int len) {
364
27.9k
  GifBufferContext* const ctx = (GifBufferContext*)gif->UserData;
365
27.9k
  if (ctx->offset + len > ctx->size) {
366
72
    len = (int)(ctx->size - ctx->offset);
367
72
  }
368
27.9k
  if (len > 0) {
369
27.9k
    memcpy(dest, ctx->data + ctx->offset, len);
370
27.9k
    ctx->offset += len;
371
27.9k
  }
372
27.9k
  return len;
373
27.9k
}
374
375
// Returns true if this is a valid GIF bitstream.
376
590
static int IsGIF(const WebPData* const data) {
377
590
  return data->size > GIF_STAMP_LEN &&
378
560
         (!memcmp(GIF_STAMP, data->bytes, GIF_STAMP_LEN) ||
379
560
          !memcmp(GIF87_STAMP, data->bytes, GIF_STAMP_LEN) ||
380
495
          !memcmp(GIF89_STAMP, data->bytes, GIF_STAMP_LEN));
381
590
}
382
383
// GIFLIB_MAJOR is only defined in libgif >= 4.2.0.
384
#if defined(GIFLIB_MAJOR) && defined(GIFLIB_MINOR)
385
#define LOCAL_GIF_VERSION ((GIFLIB_MAJOR << 8) | GIFLIB_MINOR)
386
#define LOCAL_GIF_PREREQ(maj, min) (LOCAL_GIF_VERSION >= (((maj) << 8) | (min)))
387
#else
388
#define LOCAL_GIF_VERSION 0
389
#define LOCAL_GIF_PREREQ(maj, min) 0
390
#endif
391
392
#if !LOCAL_GIF_PREREQ(5, 0)
393
394
// Added in v5.0
395
typedef struct {
396
  int DisposalMode;
397
#define DISPOSAL_UNSPECIFIED 0  // No disposal specified
398
#define DISPOSE_DO_NOT 1        // Leave image in place
399
#define DISPOSE_BACKGROUND 2    // Set area to background color
400
#define DISPOSE_PREVIOUS 3      // Restore to previous content
401
  int UserInputFlag;            // User confirmation required before disposal
402
  int DelayTime;                // Pre-display delay in 0.01sec units
403
  int TransparentColor;         // Palette index for transparency, -1 if none
404
#define NO_TRANSPARENT_COLOR -1
405
} GraphicsControlBlock;
406
407
static int DGifExtensionToGCB(const size_t GifExtensionLength,
408
                              const GifByteType* GifExtension,
409
                              GraphicsControlBlock* gcb) {
410
  if (GifExtensionLength != 4) {
411
    return GIF_ERROR;
412
  }
413
  gcb->DisposalMode = (GifExtension[0] >> 2) & 0x07;
414
  gcb->UserInputFlag = (GifExtension[0] & 0x02) != 0;
415
  gcb->DelayTime = GifExtension[1] | (GifExtension[2] << 8);
416
  if (GifExtension[0] & 0x01) {
417
    gcb->TransparentColor = (int)GifExtension[3];
418
  } else {
419
    gcb->TransparentColor = NO_TRANSPARENT_COLOR;
420
  }
421
  return GIF_OK;
422
}
423
424
static int DGifSavedExtensionToGCB(GifFileType* GifFile, int ImageIndex,
425
                                   GraphicsControlBlock* gcb) {
426
  int i;
427
  if (ImageIndex < 0 || ImageIndex > GifFile->ImageCount - 1) {
428
    return GIF_ERROR;
429
  }
430
  gcb->DisposalMode = DISPOSAL_UNSPECIFIED;
431
  gcb->UserInputFlag = 0;
432
  gcb->DelayTime = 0;
433
  gcb->TransparentColor = NO_TRANSPARENT_COLOR;
434
435
  for (i = 0; i < GifFile->SavedImages[ImageIndex].ExtensionBlockCount; i++) {
436
    ExtensionBlock* ep = &GifFile->SavedImages[ImageIndex].ExtensionBlocks[i];
437
    if (ep->Function == GRAPHICS_EXT_FUNC_CODE) {
438
      return DGifExtensionToGCB(ep->ByteCount, (const GifByteType*)ep->Bytes,
439
                                gcb);
440
    }
441
  }
442
  return GIF_ERROR;
443
}
444
445
#define CONTINUE_EXT_FUNC_CODE 0x00
446
447
// Signature was changed in v5.0
448
#define DGifOpenFileName(a, b) DGifOpenFileName(a)
449
450
#endif  // !LOCAL_GIF_PREREQ(5, 0)
451
452
// Signature changed in v5.1
453
#if !LOCAL_GIF_PREREQ(5, 1)
454
#define DGifCloseFile(a, b) DGifCloseFile(a)
455
#endif
456
457
static int IsKeyFrameGIF(const GifImageDesc* prev_desc, int prev_dispose,
458
                         const DecodedFrame* const prev_frame, int canvas_width,
459
0
                         int canvas_height) {
460
0
  if (prev_frame == NULL) return 1;
461
0
  if (prev_dispose == DISPOSE_BACKGROUND) {
462
0
    if (IsFullFrame(prev_desc->Width, prev_desc->Height, canvas_width,
463
0
                    canvas_height)) {
464
0
      return 1;
465
0
    }
466
0
    if (prev_frame->is_key_frame) return 1;
467
0
  }
468
0
  return 0;
469
0
}
470
471
0
static int GetTransparentIndexGIF(GifFileType* gif) {
472
0
  GraphicsControlBlock first_gcb;
473
0
  memset(&first_gcb, 0, sizeof(first_gcb));
474
0
  DGifSavedExtensionToGCB(gif, 0, &first_gcb);
475
0
  return first_gcb.TransparentColor;
476
0
}
477
478
0
static uint32_t GetBackgroundColorGIF(GifFileType* gif) {
479
0
  const int transparent_index = GetTransparentIndexGIF(gif);
480
0
  const ColorMapObject* const color_map = gif->SColorMap;
481
0
  if (transparent_index != NO_TRANSPARENT_COLOR &&
482
0
      gif->SBackGroundColor == transparent_index) {
483
0
    return 0x00000000;  // Special case: transparent black.
484
0
  } else if (color_map == NULL || color_map->Colors == NULL ||
485
0
             gif->SBackGroundColor >= color_map->ColorCount) {
486
0
    return 0xffffffff;  // Invalid: assume white.
487
0
  } else {
488
0
    const GifColorType color = color_map->Colors[gif->SBackGroundColor];
489
0
    return (0xffu << 24) | (color.Red << 16) | (color.Green << 8) |
490
0
           (color.Blue << 0);
491
0
  }
492
0
}
493
494
// Find appropriate app extension and get loop count from the next extension.
495
// We use Chrome's interpretation of the 'loop_count' semantics:
496
//   if not present -> loop once
497
//   if present and loop_count == 0, return 0 ('infinite').
498
//   if present and loop_count != 0, it's the number of *extra* loops
499
//     so we need to return loop_count + 1 as total loop number.
500
0
static uint32_t GetLoopCountGIF(const GifFileType* const gif) {
501
0
  int i;
502
0
  for (i = 0; i < gif->ImageCount; ++i) {
503
0
    const SavedImage* const image = &gif->SavedImages[i];
504
0
    int j;
505
0
    for (j = 0; (j + 1) < image->ExtensionBlockCount; ++j) {
506
0
      const ExtensionBlock* const eb1 = image->ExtensionBlocks + j;
507
0
      const ExtensionBlock* const eb2 = image->ExtensionBlocks + j + 1;
508
0
      const char* const signature = (const char*)eb1->Bytes;
509
0
      const int signature_is_ok =
510
0
          (eb1->Function == APPLICATION_EXT_FUNC_CODE) &&
511
0
          (eb1->ByteCount == 11) &&
512
0
          (!memcmp(signature, "NETSCAPE2.0", 11) ||
513
0
           !memcmp(signature, "ANIMEXTS1.0", 11));
514
0
      if (signature_is_ok && eb2->Function == CONTINUE_EXT_FUNC_CODE &&
515
0
          eb2->ByteCount >= 3 && eb2->Bytes[0] == 1) {
516
0
        const uint32_t extra_loop =
517
0
            ((uint32_t)(eb2->Bytes[2]) << 8) + ((uint32_t)(eb2->Bytes[1]) << 0);
518
0
        return (extra_loop > 0) ? extra_loop + 1 : 0;
519
0
      }
520
0
    }
521
0
  }
522
0
  return 1;  // Default.
523
0
}
524
525
// Get duration of 'n'th frame in milliseconds.
526
0
static int GetFrameDurationGIF(GifFileType* gif, int n) {
527
0
  GraphicsControlBlock gcb;
528
0
  memset(&gcb, 0, sizeof(gcb));
529
0
  DGifSavedExtensionToGCB(gif, n, &gcb);
530
0
  return gcb.DelayTime * 10;
531
0
}
532
533
// Returns true if frame 'target' completely covers 'covered'.
534
static int CoversFrameGIF(const GifImageDesc* const target,
535
0
                          const GifImageDesc* const covered) {
536
0
  return target->Left <= covered->Left &&
537
0
         covered->Left + covered->Width <= target->Left + target->Width &&
538
0
         target->Top <= covered->Top &&
539
0
         covered->Top + covered->Height <= target->Top + target->Height;
540
0
}
541
542
WEBP_NODISCARD
543
static int RemapPixelsGIF(const uint8_t* const src,
544
                          const ColorMapObject* const cmap,
545
0
                          int transparent_color, int len, uint8_t* dst) {
546
0
  int i;
547
0
  for (i = 0; i < len; ++i) {
548
0
    if (src[i] != transparent_color) {
549
      // If a pixel in the current frame is transparent, we don't modify it, so
550
      // that we can see-through the corresponding pixel from an earlier frame.
551
0
      GifColorType c;
552
0
      if (src[i] >= cmap->ColorCount) {
553
0
        fprintf(stderr, "Invalid color index: %d\n", src[i]);
554
0
        return 0;
555
0
      }
556
0
      c = cmap->Colors[src[i]];
557
0
      dst[4 * i + 0] = c.Red;
558
0
      dst[4 * i + 1] = c.Green;
559
0
      dst[4 * i + 2] = c.Blue;
560
0
      dst[4 * i + 3] = 0xff;
561
0
    }
562
0
  }
563
0
  return 1;
564
0
}
565
566
WEBP_NODISCARD
567
static int ReadFrameGIF(const SavedImage* const gif_image,
568
                        const ColorMapObject* cmap, int transparent_color,
569
0
                        uint32_t out_stride, uint8_t* const dst) {
570
0
  const GifImageDesc* image_desc = &gif_image->ImageDesc;
571
0
  const uint8_t* in;
572
0
  uint8_t* out;
573
0
  int j;
574
0
  size_t size, offset;
575
576
0
  if (image_desc->ColorMap) cmap = image_desc->ColorMap;
577
578
0
  if (cmap == NULL || cmap->ColorCount != (1 << cmap->BitsPerPixel)) {
579
0
    fprintf(stderr, "Potentially corrupt color map.\n");
580
0
    return 0;
581
0
  }
582
583
0
  in = (const uint8_t*)gif_image->RasterBits;
584
0
  if (!CheckMultiplicationOverflow(image_desc->Top, out_stride, &size) ||
585
0
      !CheckAdditionOverflow(size, image_desc->Left * kNumChannels, &offset)) {
586
0
    fprintf(stderr, "Invalid image description.\n");
587
0
    return 0;
588
0
  }
589
0
  out = dst + offset;
590
591
0
  for (j = 0; j < image_desc->Height; ++j) {
592
0
    if (!RemapPixelsGIF(in, cmap, transparent_color, image_desc->Width, out)) {
593
0
      return 0;
594
0
    }
595
0
    in += image_desc->Width;
596
0
    out += out_stride;
597
0
  }
598
0
  return 1;
599
0
}
600
601
// Read animated GIF bitstream from 'gif_data' into 'AnimatedImage' struct.
602
static int ReadAnimatedGIF(const char filename[],
603
                           const WebPData* const gif_data,
604
                           AnimatedImage* const image, int dump_frames,
605
92
                           const char dump_folder[]) {
606
92
  uint32_t frame_count;
607
92
  uint32_t canvas_width, canvas_height;
608
92
  uint32_t i;
609
92
  int gif_error;
610
92
  GifFileType* gif;
611
92
  GifBufferContext ctx;
612
613
92
  ctx.data = gif_data->bytes;
614
92
  ctx.size = gif_data->size;
615
92
  ctx.offset = 0;
616
92
  gif = DGifOpen(&ctx, MemoryReadGIF, &gif_error);
617
92
  if (gif == NULL) {
618
27
    WFPRINTF(stderr, "Could not read GIF from memory: %s.\n",
619
27
             (const W_CHAR*)filename);
620
27
    return 0;
621
27
  }
622
623
65
  gif_error = DGifSlurp(gif);
624
65
  if (gif_error != GIF_OK) {
625
65
    WFPRINTF(stderr, "Could not parse image: %s.\n", (const W_CHAR*)filename);
626
65
    GIFDisplayError(gif, gif_error);
627
65
    DGifCloseFile(gif, NULL);
628
65
    return 0;
629
65
  }
630
631
  // Animation properties.
632
0
  image->canvas_width = (uint32_t)gif->SWidth;
633
0
  image->canvas_height = (uint32_t)gif->SHeight;
634
0
  if (image->canvas_width > MAX_CANVAS_SIZE ||
635
0
      image->canvas_height > MAX_CANVAS_SIZE) {
636
0
    fprintf(stderr, "Invalid canvas dimension: %d x %d\n", image->canvas_width,
637
0
            image->canvas_height);
638
0
    DGifCloseFile(gif, NULL);
639
0
    return 0;
640
0
  }
641
0
  image->loop_count = GetLoopCountGIF(gif);
642
0
  image->bgcolor = GetBackgroundColorGIF(gif);
643
644
0
  frame_count = (uint32_t)gif->ImageCount;
645
0
  if (frame_count == 0) {
646
0
    DGifCloseFile(gif, NULL);
647
0
    return 0;
648
0
  }
649
650
0
  if (image->canvas_width == 0 || image->canvas_height == 0) {
651
0
    image->canvas_width = gif->SavedImages[0].ImageDesc.Width;
652
0
    image->canvas_height = gif->SavedImages[0].ImageDesc.Height;
653
0
    gif->SavedImages[0].ImageDesc.Left = 0;
654
0
    gif->SavedImages[0].ImageDesc.Top = 0;
655
0
    if (image->canvas_width == 0 || image->canvas_height == 0) {
656
0
      fprintf(stderr, "Invalid canvas size in GIF.\n");
657
0
      DGifCloseFile(gif, NULL);
658
0
      return 0;
659
0
    }
660
0
  }
661
  // Allocate frames.
662
0
  if (!AllocateFrames(image, frame_count)) {
663
0
    DGifCloseFile(gif, NULL);
664
0
    return 0;
665
0
  }
666
667
0
  canvas_width = image->canvas_width;
668
0
  canvas_height = image->canvas_height;
669
670
  // Decode and reconstruct frames.
671
0
  for (i = 0; i < frame_count; ++i) {
672
0
    const uint32_t canvas_width_in_bytes = canvas_width * kNumChannels;
673
0
    const SavedImage* const curr_gif_image = &gif->SavedImages[i];
674
0
    GraphicsControlBlock curr_gcb;
675
0
    DecodedFrame* curr_frame;
676
0
    uint8_t* curr_rgba;
677
0
    const int left = curr_gif_image->ImageDesc.Left;
678
0
    const int top = curr_gif_image->ImageDesc.Top;
679
0
    const int width = curr_gif_image->ImageDesc.Width;
680
0
    const int height = curr_gif_image->ImageDesc.Height;
681
682
0
    if (left < 0 || top < 0 || width <= 0 || height <= 0 ||
683
0
        (uint32_t)(left + width) > canvas_width ||
684
0
        (uint32_t)(top + height) > canvas_height) {
685
0
      DGifCloseFile(gif, NULL);
686
0
      return 0;
687
0
    }
688
0
    assert((uint32_t)width <= kGifDimMax && (uint32_t)height <= kGifDimMax);
689
690
0
    memset(&curr_gcb, 0, sizeof(curr_gcb));
691
0
    DGifSavedExtensionToGCB(gif, i, &curr_gcb);
692
693
0
    curr_frame = &image->frames[i];
694
0
    curr_rgba = curr_frame->rgba;
695
0
    curr_frame->duration = GetFrameDurationGIF(gif, i);
696
    // Force frames with a small or no duration to 100ms to be consistent
697
    // with web browsers and other transcoding tools (like gif2webp itself).
698
0
    if (curr_frame->duration <= 10) curr_frame->duration = 100;
699
700
0
    if (i == 0) {  // Initialize as transparent.
701
0
      curr_frame->is_key_frame = 1;
702
0
      if (!ZeroFillCanvas(curr_rgba, canvas_width, canvas_height)) {
703
0
        DGifCloseFile(gif, NULL);
704
0
        return 0;
705
0
      }
706
0
    } else {
707
0
      DecodedFrame* const prev_frame = &image->frames[i - 1];
708
0
      const GifImageDesc* const prev_desc = &gif->SavedImages[i - 1].ImageDesc;
709
0
      GraphicsControlBlock prev_gcb;
710
0
      memset(&prev_gcb, 0, sizeof(prev_gcb));
711
0
      DGifSavedExtensionToGCB(gif, i - 1, &prev_gcb);
712
713
0
      curr_frame->is_key_frame =
714
0
          IsKeyFrameGIF(prev_desc, prev_gcb.DisposalMode, prev_frame,
715
0
                        canvas_width, canvas_height);
716
717
0
      if (curr_frame->is_key_frame) {  // Initialize as transparent.
718
0
        if (!ZeroFillCanvas(curr_rgba, canvas_width, canvas_height)) {
719
0
          DGifCloseFile(gif, NULL);
720
0
          return 0;
721
0
        }
722
0
      } else {
723
0
        int prev_frame_disposed, curr_frame_opaque;
724
0
        int prev_frame_completely_covered;
725
        // Initialize with previous canvas.
726
0
        uint8_t* const prev_rgba = image->frames[i - 1].rgba;
727
0
        if (!CopyCanvas(prev_rgba, curr_rgba, canvas_width, canvas_height)) {
728
0
          DGifCloseFile(gif, NULL);
729
0
          return 0;
730
0
        }
731
732
        // Dispose previous frame rectangle.
733
0
        prev_frame_disposed = (prev_gcb.DisposalMode == DISPOSE_BACKGROUND ||
734
0
                               prev_gcb.DisposalMode == DISPOSE_PREVIOUS);
735
0
        curr_frame_opaque = (curr_gcb.TransparentColor == NO_TRANSPARENT_COLOR);
736
0
        prev_frame_completely_covered =
737
0
            curr_frame_opaque &&
738
0
            CoversFrameGIF(&curr_gif_image->ImageDesc, prev_desc);
739
740
0
        if (prev_frame_disposed && !prev_frame_completely_covered) {
741
0
          switch (prev_gcb.DisposalMode) {
742
0
            case DISPOSE_BACKGROUND: {
743
0
              if (!ZeroFillFrameRect(curr_rgba, canvas_width_in_bytes,
744
0
                                     prev_desc->Left, prev_desc->Top,
745
0
                                     prev_desc->Width, prev_desc->Height)) {
746
0
                DGifCloseFile(gif, NULL);
747
0
                return 0;
748
0
              }
749
0
              break;
750
0
            }
751
0
            case DISPOSE_PREVIOUS: {
752
0
              int src_frame_num = i - 2;
753
0
              while (src_frame_num >= 0) {
754
0
                GraphicsControlBlock src_frame_gcb;
755
0
                memset(&src_frame_gcb, 0, sizeof(src_frame_gcb));
756
0
                DGifSavedExtensionToGCB(gif, src_frame_num, &src_frame_gcb);
757
0
                if (src_frame_gcb.DisposalMode != DISPOSE_PREVIOUS) break;
758
0
                --src_frame_num;
759
0
              }
760
0
              if (src_frame_num >= 0) {
761
                // Restore pixels inside previous frame rectangle to
762
                // corresponding pixels in source canvas.
763
0
                uint8_t* const src_frame_rgba =
764
0
                    image->frames[src_frame_num].rgba;
765
0
                if (!CopyFrameRectangle(src_frame_rgba, curr_rgba,
766
0
                                        canvas_width_in_bytes, prev_desc->Left,
767
0
                                        prev_desc->Top, prev_desc->Width,
768
0
                                        prev_desc->Height)) {
769
0
                  DGifCloseFile(gif, NULL);
770
0
                  return 0;
771
0
                }
772
0
              } else {
773
                // Source canvas doesn't exist. So clear previous frame
774
                // rectangle to background.
775
0
                if (!ZeroFillFrameRect(curr_rgba, canvas_width_in_bytes,
776
0
                                       prev_desc->Left, prev_desc->Top,
777
0
                                       prev_desc->Width, prev_desc->Height)) {
778
0
                  DGifCloseFile(gif, NULL);
779
0
                  return 0;
780
0
                }
781
0
              }
782
0
              break;
783
0
            }
784
0
            default:
785
0
              break;  // Nothing to do.
786
0
          }
787
0
        }
788
0
      }
789
0
    }
790
791
    // Decode current frame.
792
0
    if (!ReadFrameGIF(curr_gif_image, gif->SColorMap, curr_gcb.TransparentColor,
793
0
                      canvas_width_in_bytes, curr_rgba)) {
794
0
      DGifCloseFile(gif, NULL);
795
0
      return 0;
796
0
    }
797
798
0
    if (dump_frames) {
799
0
      if (!DumpFrame(filename, dump_folder, i, curr_rgba, canvas_width,
800
0
                     canvas_height)) {
801
0
        DGifCloseFile(gif, NULL);
802
0
        return 0;
803
0
      }
804
0
    }
805
0
  }
806
0
  image->format = ANIM_GIF;
807
0
  DGifCloseFile(gif, NULL);
808
0
  return 1;
809
0
}
810
811
#else
812
813
static int IsGIF(const WebPData* const data) {
814
  (void)data;
815
  return 0;
816
}
817
818
static int ReadAnimatedGIF(const char filename[],
819
                           const WebPData* const gif_data,
820
                           AnimatedImage* const image, int dump_frames,
821
                           const char dump_folder[]) {
822
  (void)filename;
823
  (void)gif_data;
824
  (void)image;
825
  (void)dump_frames;
826
  (void)dump_folder;
827
  fprintf(stderr,
828
          "GIF support not compiled. Please install the libgif-dev "
829
          "package before building.\n");
830
  return 0;
831
}
832
833
#endif  // WEBP_HAVE_GIF
834
835
// -----------------------------------------------------------------------------
836
837
int ReadAnimatedImage(const char filename[], AnimatedImage* const image,
838
0
                      int dump_frames, const char dump_folder[]) {
839
0
  int ok = 0;
840
0
  WebPData webp_data;
841
842
0
  WebPDataInit(&webp_data);
843
844
0
  if (!ImgIoUtilReadFile(filename, &webp_data.bytes, &webp_data.size)) {
845
0
    WFPRINTF(stderr, "Error reading file: %s\n", (const W_CHAR*)filename);
846
0
    return 0;
847
0
  }
848
849
0
  ok = ReadAnimatedImageFromMemory(filename, webp_data.bytes, webp_data.size,
850
0
                                   image, dump_frames, dump_folder);
851
0
  if (!ok) {
852
0
    WFPRINTF(stderr, "Error parsing image: %s\n", (const W_CHAR*)filename);
853
0
  }
854
855
0
  WebPDataClear(&webp_data);
856
0
  return ok;
857
0
}
858
859
int ReadAnimatedImageFromMemory(const char filename[],
860
                                const uint8_t* const data, size_t size,
861
                                AnimatedImage* const image, int dump_frames,
862
7.43k
                                const char dump_folder[]) {
863
7.43k
  int ok = 0;
864
7.43k
  WebPData webp_data;
865
866
7.43k
  webp_data.bytes = data;
867
7.43k
  webp_data.size = size;
868
7.43k
  memset(image, 0, sizeof(*image));
869
870
7.43k
  if (IsWebP(&webp_data)) {
871
6.84k
    ok =
872
6.84k
        ReadAnimatedWebP(filename, &webp_data, image, dump_frames, dump_folder);
873
6.84k
  } else if (IsGIF(&webp_data)) {
874
92
    ok = ReadAnimatedGIF(filename, &webp_data, image, dump_frames, dump_folder);
875
498
  } else {
876
498
    WFPRINTF(stderr,
877
498
             "Unknown file type: %s. Supported file types are WebP and GIF\n",
878
498
             (const W_CHAR*)filename);
879
498
    ok = 0;
880
498
  }
881
7.43k
  if (!ok) ClearAnimatedImage(image);
882
7.43k
  return ok;
883
7.43k
}
884
885
static void Accumulate(double v1, double v2, double* const max_diff,
886
0
                       double* const sse) {
887
0
  const double diff = fabs(v1 - v2);
888
0
  if (diff > *max_diff) *max_diff = diff;
889
0
  *sse += diff * diff;
890
0
}
891
892
void GetDiffAndPSNR(const uint8_t rgba1[], const uint8_t rgba2[],
893
                    uint32_t width, uint32_t height, int premultiply,
894
0
                    int* const max_diff, double* const psnr) {
895
0
  const uint32_t stride = width * kNumChannels;
896
0
  const int kAlphaChannel = kNumChannels - 1;
897
0
  double f_max_diff = 0.;
898
0
  double sse = 0.;
899
0
  uint32_t x, y;
900
0
  for (y = 0; y < height; ++y) {
901
0
    for (x = 0; x < stride; x += kNumChannels) {
902
0
      int k;
903
0
      const size_t offset = (size_t)y * stride + x;
904
0
      const int alpha1 = rgba1[offset + kAlphaChannel];
905
0
      const int alpha2 = rgba2[offset + kAlphaChannel];
906
0
      Accumulate(alpha1, alpha2, &f_max_diff, &sse);
907
0
      if (!premultiply) {
908
0
        for (k = 0; k < kAlphaChannel; ++k) {
909
0
          Accumulate(rgba1[offset + k], rgba2[offset + k], &f_max_diff, &sse);
910
0
        }
911
0
      } else {
912
        // premultiply R/G/B channels with alpha value
913
0
        for (k = 0; k < kAlphaChannel; ++k) {
914
0
          Accumulate(rgba1[offset + k] * alpha1 / 255.,
915
0
                     rgba2[offset + k] * alpha2 / 255., &f_max_diff, &sse);
916
0
        }
917
0
      }
918
0
    }
919
0
  }
920
0
  *max_diff = (int)f_max_diff;
921
0
  if (*max_diff == 0) {
922
0
    *psnr = 99.;  // PSNR when images are identical.
923
0
  } else {
924
0
    sse /= stride * height;
925
0
    assert(sse != 0.0);
926
0
    *psnr = 4.3429448 * log(255. * 255. / sse);
927
0
  }
928
0
}
929
930
void GetAnimatedImageVersions(int* const decoder_version,
931
0
                              int* const demux_version) {
932
0
  *decoder_version = WebPGetDecoderVersion();
933
0
  *demux_version = WebPGetDemuxVersion();
934
0
}