text: // Copyright 2015 Google Inc. All Rights Reserved.
  //
  // Use of this source code is governed by a BSD-style license
  // that can be found in the COPYING file in the root of the source
  // tree. An additional intellectual property rights grant can be found
  // in the file PATENTS. All contributing project authors may
  // be found in the AUTHORS file in the root of the source tree.
  // -----------------------------------------------------------------------------
  //
  // Utilities for animated images
  
  #include "./anim_util.h"
  
  #include <assert.h>
  #include <math.h>
  #include <stdio.h>
  #include <string.h>
  
  #ifdef WEBP_HAVE_GIF
  #include <gif_lib.h>
  #endif
  #include "webp/format_constants.h"
  #include "webp/decode.h"
  #include "webp/demux.h"
  #include "../imageio/imageio_util.h"
  
  #if defined(_MSC_VER) && _MSC_VER < 1900
  #define snprintf _snprintf
  #endif
  
  static const int kNumChannels = 4;
  
  // -----------------------------------------------------------------------------
  // Common utilities.
  
  // Returns true if the frame covers the full canvas.
  static int IsFullFrame(int width, int height,
                         int canvas_width, int canvas_height) {
    return (width == canvas_width && height == canvas_height);
  }
  
  static int CheckSizeForOverflow(uint64_t size) {
    return (size == (size_t)size);
  }
  
  static int AllocateFrames(AnimatedImage* const image, uint32_t num_frames) {
    uint32_t i;
    uint8_t* mem = NULL;
    DecodedFrame* frames = NULL;
    const uint64_t rgba_size =
        (uint64_t)image->canvas_width * kNumChannels * image->canvas_height;
    const uint64_t total_size = (uint64_t)num_frames * rgba_size * sizeof(*mem);
    const uint64_t total_frame_size = (uint64_t)num_frames * sizeof(*frames);
    if (!CheckSizeForOverflow(total_size) ||
        !CheckSizeForOverflow(total_frame_size)) {
      return 0;
    }
    mem = (uint8_t*)malloc((size_t)total_size);
    frames = (DecodedFrame*)malloc((size_t)total_frame_size);
  
    if (mem == NULL || frames == NULL) {
      free(mem);
      free(frames);
      return 0;
    }
    free(image->raw_mem);
    image->num_frames = num_frames;
    image->frames = frames;
    for (i = 0; i < num_frames; ++i) {
      frames[i].rgba = mem + i * rgba_size;
      frames[i].duration = 0;
      frames[i].is_key_frame = 0;
    }
    image->raw_mem = mem;
    return 1;
  }
  
  void ClearAnimatedImage(AnimatedImage* const image) {
    if (image != NULL) {
      free(image->raw_mem);
      free(image->frames);
      image->num_frames = 0;
      image->frames = NULL;
      image->raw_mem = NULL;
    }
  }
  
  // Clear the canvas to transparent.
  static void ZeroFillCanvas(uint8_t* rgba,
                             uint32_t canvas_width, uint32_t canvas_height) {
    memset(rgba, 0, canvas_width * kNumChannels * canvas_height);
  }
  
  // Clear given frame rectangle to transparent.
  static void ZeroFillFrameRect(uint8_t* rgba, int rgba_stride, int x_offset,
                                int y_offset, int width, int height) {
    int j;
    assert(width * kNumChannels <= rgba_stride);
    rgba += y_offset * rgba_stride + x_offset * kNumChannels;
    for (j = 0; j < height; ++j) {
      memset(rgba, 0, width * kNumChannels);
      rgba += rgba_stride;
    }
  }
  
  // Copy width * height pixels from 'src' to 'dst'.
  static void CopyCanvas(const uint8_t* src, uint8_t* dst,
                         uint32_t width, uint32_t height) {
    assert(src != NULL && dst != NULL);
    memcpy(dst, src, width * kNumChannels * height);
  }
  
  // Copy pixels in the given rectangle from 'src' to 'dst' honoring the 'stride'.
  static void CopyFrameRectangle(const uint8_t* src, uint8_t* dst, int stride,
                                 int x_offset, int y_offset,
                                 int width, int height) {
    int j;
    const int width_in_bytes = width * kNumChannels;
    const size_t offset = y_offset * stride + x_offset * kNumChannels;
    assert(width_in_bytes <= stride);
    src += offset;
    dst += offset;
    for (j = 0; j < height; ++j) {
      memcpy(dst, src, width_in_bytes);
      src += stride;
      dst += stride;
    }
  }
  
  // Canonicalize all transparent pixels to transparent black to aid comparison.
  static void CleanupTransparentPixels(uint32_t* rgba,
                                       uint32_t width, uint32_t height) {
    const uint32_t* const rgba_end = rgba + width * height;
    while (rgba < rgba_end) {
      const uint8_t alpha = (*rgba >> 24) & 0xff;
      if (alpha == 0) {
        *rgba = 0;
      }
      ++rgba;
    }
  }
  
  // Dump frame to a PAM file. Returns true on success.
  static int DumpFrame(const char filename[], const char dump_folder[],
                       uint32_t frame_num, const uint8_t rgba[],
                       int canvas_width, int canvas_height) {
    int ok = 0;
    size_t max_len;
    int y;
    const char* base_name = NULL;
    char* file_name = NULL;
    FILE* f = NULL;
    const char* row;
  
    if (dump_folder == NULL) dump_folder = ".";
  
    base_name = strrchr(filename, '/');
    base_name = (base_name == NULL) ? filename : base_name + 1;
    max_len = strlen(dump_folder) + 1 + strlen(base_name)
            + strlen("_frame_") + strlen(".pam") + 8;
    file_name = (char*)malloc(max_len * sizeof(*file_name));
    if (file_name == NULL) goto End;
  
    if (snprintf(file_name, max_len, "%s/%s_frame_%d.pam",
                 dump_folder, base_name, frame_num) < 0) {
      fprintf(stderr, "Error while generating file name\n");
      goto End;
    }
  
    f = fopen(file_name, "wb");
    if (f == NULL) {
      fprintf(stderr, "Error opening file for writing: %s\n", file_name);
      ok = 0;
      goto End;
    }
    if (fprintf(f, "P7\nWIDTH %d\nHEIGHT %d\n"
                "DEPTH 4\nMAXVAL 255\nTUPLTYPE RGB_ALPHA\nENDHDR\n",
                canvas_width, canvas_height) < 0) {
      fprintf(stderr, "Write error for file %s\n", file_name);
      goto End;
    }
    row = (const char*)rgba;
    for (y = 0; y < canvas_height; ++y) {
      if (fwrite(row, canvas_width * kNumChannels, 1, f) != 1) {
        fprintf(stderr, "Error writing to file: %s\n", file_name);
        goto End;
      }
      row += canvas_width * kNumChannels;
    }
    ok = 1;
   End:
    if (f != NULL) fclose(f);
    free(file_name);
    return ok;
  }
  
  // -----------------------------------------------------------------------------
  // WebP Decoding.
  
  // Returns true if this is a valid WebP bitstream.
  static int IsWebP(const WebPData* const webp_data) {
    return (WebPGetInfo(webp_data->bytes, webp_data->size, NULL, NULL) != 0);
  }
  
  // Read animated WebP bitstream 'webp_data' into 'AnimatedImage' struct.
  static int ReadAnimatedWebP(const char filename[],
                              const WebPData* const webp_data,
                              AnimatedImage* const image, int dump_frames,
                              const char dump_folder[]) {
    int ok = 0;
    int dump_ok = 1;
    uint32_t frame_index = 0;
    int prev_frame_timestamp = 0;
    WebPAnimDecoder* dec;
    WebPAnimInfo anim_info;
  
    memset(image, 0, sizeof(*image));
  
    dec = WebPAnimDecoderNew(webp_data, NULL);
    if (dec == NULL) {
      fprintf(stderr, "Error parsing image: %s\n", filename);
      goto End;
    }
  
    if (!WebPAnimDecoderGetInfo(dec, &anim_info)) {
      fprintf(stderr, "Error getting global info about the animation\n");
      goto End;
    }
  
    // Animation properties.
    image->canvas_width = anim_info.canvas_width;
    image->canvas_height = anim_info.canvas_height;
    image->loop_count = anim_info.loop_count;
    image->bgcolor = anim_info.bgcolor;
  
    // Allocate frames.
    if (!AllocateFrames(image, anim_info.frame_count)) return 0;
  
    // Decode frames.
    while (WebPAnimDecoderHasMoreFrames(dec)) {
      DecodedFrame* curr_frame;
      uint8_t* curr_rgba;
      uint8_t* frame_rgba;
      int timestamp;
  
      if (!WebPAnimDecoderGetNext(dec, &frame_rgba, &timestamp)) {
        fprintf(stderr, "Error decoding frame #%u\n", frame_index);
        goto End;
      }
      assert(frame_index < anim_info.frame_count);
      curr_frame = &image->frames[frame_index];
      curr_rgba = curr_frame->rgba;
      curr_frame->duration = timestamp - prev_frame_timestamp;
      curr_frame->is_key_frame = 0;  // Unused.
      memcpy(curr_rgba, frame_rgba,
             image->canvas_width * kNumChannels * image->canvas_height);
  
      // Needed only because we may want to compare with GIF later.
      CleanupTransparentPixels((uint32_t*)curr_rgba,
                               image->canvas_width, image->canvas_height);
  
      if (dump_frames && dump_ok) {
        dump_ok = DumpFrame(filename, dump_folder, frame_index, curr_rgba,
                            image->canvas_width, image->canvas_height);
        if (!dump_ok) {  // Print error once, but continue decode loop.
          fprintf(stderr, "Error dumping frames to %s\n", dump_folder);
        }
      }
  
      ++frame_index;
      prev_frame_timestamp = timestamp;
    }
    ok = dump_ok;
  
   End:
    WebPAnimDecoderDelete(dec);
    return ok;
  }
  
  // -----------------------------------------------------------------------------
  // GIF Decoding.
  
  #ifdef WEBP_HAVE_GIF
  
  // Returns true if this is a valid GIF bitstream.
  static int IsGIF(const WebPData* const data) {
    return data->size > GIF_STAMP_LEN &&
           (!memcmp(GIF_STAMP, data->bytes, GIF_STAMP_LEN) ||
            !memcmp(GIF87_STAMP, data->bytes, GIF_STAMP_LEN) ||
            !memcmp(GIF89_STAMP, data->bytes, GIF_STAMP_LEN));
  }
  
  // GIFLIB_MAJOR is only defined in libgif >= 4.2.0.
  #if defined(GIFLIB_MAJOR) && defined(GIFLIB_MINOR)
  # define LOCAL_GIF_VERSION ((GIFLIB_MAJOR << 8) | GIFLIB_MINOR)
  # define LOCAL_GIF_PREREQ(maj, min) \
      (LOCAL_GIF_VERSION >= (((maj) << 8) | (min)))
  #else
  # define LOCAL_GIF_VERSION 0
  # define LOCAL_GIF_PREREQ(maj, min) 0
  #endif
  
  #if !LOCAL_GIF_PREREQ(5, 0)
  
  // Added in v5.0
  typedef struct {
    int DisposalMode;
  #define DISPOSAL_UNSPECIFIED      0       // No disposal specified
  #define DISPOSE_DO_NOT            1       // Leave image in place
  #define DISPOSE_BACKGROUND        2       // Set area to background color
  #define DISPOSE_PREVIOUS          3       // Restore to previous content
    int UserInputFlag;       // User confirmation required before disposal
    int DelayTime;           // Pre-display delay in 0.01sec units
    int TransparentColor;    // Palette index for transparency, -1 if none
  #define NO_TRANSPARENT_COLOR     -1
  } GraphicsControlBlock;
  
  static int DGifExtensionToGCB(const size_t GifExtensionLength,
                                const GifByteType* GifExtension,
                                GraphicsControlBlock* gcb) {
    if (GifExtensionLength != 4) {
      return GIF_ERROR;
    }
    gcb->DisposalMode = (GifExtension[0] >> 2) & 0x07;
    gcb->UserInputFlag = (GifExtension[0] & 0x02) != 0;
    gcb->DelayTime = GifExtension[1] | (GifExtension[2] << 8);
    if (GifExtension[0] & 0x01) {
      gcb->TransparentColor = (int)GifExtension[3];
    } else {
      gcb->TransparentColor = NO_TRANSPARENT_COLOR;
    }
    return GIF_OK;
  }
  
  static int DGifSavedExtensionToGCB(GifFileType* GifFile, int ImageIndex,
                                     GraphicsControlBlock* gcb) {
    int i;
    if (ImageIndex < 0 || ImageIndex > GifFile->ImageCount - 1) {
      return GIF_ERROR;
    }
    gcb->DisposalMode = DISPOSAL_UNSPECIFIED;
    gcb->UserInputFlag = 0;
    gcb->DelayTime = 0;
    gcb->TransparentColor = NO_TRANSPARENT_COLOR;
  
    for (i = 0; i < GifFile->SavedImages[ImageIndex].ExtensionBlockCount; i++) {
      ExtensionBlock* ep = &GifFile->SavedImages[ImageIndex].ExtensionBlocks[i];
      if (ep->Function == GRAPHICS_EXT_FUNC_CODE) {
        return DGifExtensionToGCB(
            ep->ByteCount, (const GifByteType*)ep->Bytes, gcb);
      }
    }
    return GIF_ERROR;
  }
  
  #define CONTINUE_EXT_FUNC_CODE 0x00
  
  // Signature was changed in v5.0
  #define DGifOpenFileName(a, b) DGifOpenFileName(a)
  
  #endif  // !LOCAL_GIF_PREREQ(5, 0)
  
  // Signature changed in v5.1
  #if !LOCAL_GIF_PREREQ(5, 1)
  #define DGifCloseFile(a, b) DGifCloseFile(a)
  #endif
  
  static void GIFDisplayError(const GifFileType* const gif, int gif_error) {
    // libgif 4.2.0 has retired PrintGifError() and added GifErrorString().
  #if LOCAL_GIF_PREREQ(4, 2)
  #if LOCAL_GIF_PREREQ(5, 0)
    const char* error_str =
        GifErrorString((gif == NULL) ? gif_error : gif->Error);
  #else
    const char* error_str = GifErrorString();
    (void)gif;
  #endif
    if (error_str == NULL) error_str = "Unknown error";
    fprintf(stderr, "GIFLib Error %d: %s\n", gif_error, error_str);
  #else
    (void)gif;
    fprintf(stderr, "GIFLib Error %d: ", gif_error);
    PrintGifError();
    fprintf(stderr, "\n");
  #endif
  }
  
  static int IsKeyFrameGIF(const GifImageDesc* prev_desc, int prev_dispose,
                           const DecodedFrame* const prev_frame,
                           int canvas_width, int canvas_height) {
    if (prev_frame == NULL) return 1;
    if (prev_dispose == DISPOSE_BACKGROUND) {
      if (IsFullFrame(prev_desc->Width, prev_desc->Height,
                      canvas_width, canvas_height)) {
        return 1;
      }
      if (prev_frame->is_key_frame) return 1;
    }
    return 0;
  }
  
  static int GetTransparentIndexGIF(GifFileType* gif) {
    GraphicsControlBlock first_gcb;
    memset(&first_gcb, 0, sizeof(first_gcb));
    DGifSavedExtensionToGCB(gif, 0, &first_gcb);
    return first_gcb.TransparentColor;
  }
  
  static uint32_t GetBackgroundColorGIF(GifFileType* gif) {
    const int transparent_index = GetTransparentIndexGIF(gif);
    const ColorMapObject* const color_map = gif->SColorMap;
    if (transparent_index != NO_TRANSPARENT_COLOR &&
        gif->SBackGroundColor == transparent_index) {
      return 0x00000000;  // Special case: transparent black.
    } else if (color_map == NULL || color_map->Colors == NULL
               || gif->SBackGroundColor >= color_map->ColorCount) {
      return 0xffffffff;  // Invalid: assume white.
    } else {
      const GifColorType color = color_map->Colors[gif->SBackGroundColor];
      return (0xff << 24) |
             (color.Red << 16) |
             (color.Green << 8) |
             (color.Blue << 0);
    }
  }
  
  // Find appropriate app extension and get loop count from the next extension.
  // We use Chrome's interpretation of the 'loop_count' semantics:
  //   if not present -> loop once
  //   if present and loop_count == 0, return 0 ('infinite').
  //   if present and loop_count != 0, it's the number of *extra* loops
  //     so we need to return loop_count + 1 as total loop number.
  static uint32_t GetLoopCountGIF(const GifFileType* const gif) {
    int i;
    for (i = 0; i < gif->ImageCount; ++i) {
      const SavedImage* const image = &gif->SavedImages[i];
      int j;
      for (j = 0; (j + 1) < image->ExtensionBlockCount; ++j) {
        const ExtensionBlock* const eb1 = image->ExtensionBlocks + j;
        const ExtensionBlock* const eb2 = image->ExtensionBlocks + j + 1;
        const char* const signature = (const char*)eb1->Bytes;
        const int signature_is_ok =
            (eb1->Function == APPLICATION_EXT_FUNC_CODE) &&
            (eb1->ByteCount == 11) &&
            (!memcmp(signature, "NETSCAPE2.0", 11) ||
             !memcmp(signature, "ANIMEXTS1.0", 11));
        if (signature_is_ok &&
            eb2->Function == CONTINUE_EXT_FUNC_CODE && eb2->ByteCount >= 3 &&
            eb2->Bytes[0] == 1) {
          const uint32_t extra_loop = ((uint32_t)(eb2->Bytes[2]) << 8) +
                                      ((uint32_t)(eb2->Bytes[1]) << 0);
          return (extra_loop > 0) ? extra_loop + 1 : 0;
        }
      }
    }
    return 1;  // Default.
  }
  
  // Get duration of 'n'th frame in milliseconds.
  static int GetFrameDurationGIF(GifFileType* gif, int n) {
    GraphicsControlBlock gcb;
    memset(&gcb, 0, sizeof(gcb));
    DGifSavedExtensionToGCB(gif, n, &gcb);
    return gcb.DelayTime * 10;
  }
  
  // Returns true if frame 'target' completely covers 'covered'.
  static int CoversFrameGIF(const GifImageDesc* const target,
                            const GifImageDesc* const covered) {
    return target->Left <= covered->Left &&
           covered->Left + covered->Width <= target->Left + target->Width &&
           target->Top <= covered->Top &&
           covered->Top + covered->Height <= target->Top + target->Height;
  }
  
  static void RemapPixelsGIF(const uint8_t* const src,
                             const ColorMapObject* const cmap,
                             int transparent_color, int len, uint8_t* dst) {
    int i;
    for (i = 0; i < len; ++i) {
      if (src[i] != transparent_color) {
        // If a pixel in the current frame is transparent, we don't modify it, so
        // that we can see-through the corresponding pixel from an earlier frame.
        const GifColorType c = cmap->Colors[src[i]];
        dst[4 * i + 0] = c.Red;
        dst[4 * i + 1] = c.Green;
        dst[4 * i + 2] = c.Blue;
        dst[4 * i + 3] = 0xff;
      }
    }
  }
  
  static int ReadFrameGIF(const SavedImage* const gif_image,
                          const ColorMapObject* cmap, int transparent_color,
                          int out_stride, uint8_t* const dst) {
    const GifImageDesc* image_desc = &gif_image->ImageDesc;
    const uint8_t* in;
    uint8_t* out;
    int j;
  
    if (image_desc->ColorMap) cmap = image_desc->ColorMap;
  
    if (cmap == NULL || cmap->ColorCount != (1 << cmap->BitsPerPixel)) {
      fprintf(stderr, "Potentially corrupt color map.\n");
      return 0;
    }
  
    in = (const uint8_t*)gif_image->RasterBits;
    out = dst + image_desc->Top * out_stride + image_desc->Left * kNumChannels;
  
    for (j = 0; j < image_desc->Height; ++j) {
      RemapPixelsGIF(in, cmap, transparent_color, image_desc->Width, out);
      in += image_desc->Width;
      out += out_stride;
    }
    return 1;
  }
  
  // Read animated GIF bitstream from 'filename' into 'AnimatedImage' struct.
  static int ReadAnimatedGIF(const char filename[], AnimatedImage* const image,
                             int dump_frames, const char dump_folder[]) {
    uint32_t frame_count;
    uint32_t canvas_width, canvas_height;
    uint32_t i;
    int gif_error;
    GifFileType* gif;
  
    gif = DGifOpenFileName(filename, NULL);
    if (gif == NULL) {
      fprintf(stderr, "Could not read file: %s.\n", filename);
      return 0;
    }
  
    gif_error = DGifSlurp(gif);
    if (gif_error != GIF_OK) {
      fprintf(stderr, "Could not parse image: %s.\n", filename);
      GIFDisplayError(gif, gif_error);
      DGifCloseFile(gif, NULL);
      return 0;
    }
  
    // Animation properties.
    image->canvas_width = (uint32_t)gif->SWidth;
    image->canvas_height = (uint32_t)gif->SHeight;
    if (image->canvas_width > MAX_CANVAS_SIZE ||
        image->canvas_height > MAX_CANVAS_SIZE) {
      fprintf(stderr, "Invalid canvas dimension: %d x %d\n",
              image->canvas_width, image->canvas_height);
      DGifCloseFile(gif, NULL);
      return 0;
    }
    image->loop_count = GetLoopCountGIF(gif);
    image->bgcolor = GetBackgroundColorGIF(gif);
  
    frame_count = (uint32_t)gif->ImageCount;
    if (frame_count == 0) {
      DGifCloseFile(gif, NULL);
      return 0;
    }
  
    if (image->canvas_width == 0 || image->canvas_height == 0) {
      image->canvas_width = gif->SavedImages[0].ImageDesc.Width;
      image->canvas_height = gif->SavedImages[0].ImageDesc.Height;
      gif->SavedImages[0].ImageDesc.Left = 0;
      gif->SavedImages[0].ImageDesc.Top = 0;
      if (image->canvas_width == 0 || image->canvas_height == 0) {
        fprintf(stderr, "Invalid canvas size in GIF.\n");
        DGifCloseFile(gif, NULL);
        return 0;
      }
    }
    // Allocate frames.
    AllocateFrames(image, frame_count);
  
    canvas_width = image->canvas_width;
    canvas_height = image->canvas_height;
  
    // Decode and reconstruct frames.
    for (i = 0; i < frame_count; ++i) {
      const int canvas_width_in_bytes = canvas_width * kNumChannels;
      const SavedImage* const curr_gif_image = &gif->SavedImages[i];
      GraphicsControlBlock curr_gcb;
      DecodedFrame* curr_frame;
      uint8_t* curr_rgba;
  
      memset(&curr_gcb, 0, sizeof(curr_gcb));
      DGifSavedExtensionToGCB(gif, i, &curr_gcb);
  
      curr_frame = &image->frames[i];
      curr_rgba = curr_frame->rgba;
      curr_frame->duration = GetFrameDurationGIF(gif, i);
  
      if (i == 0) {  // Initialize as transparent.
        curr_frame->is_key_frame = 1;
        ZeroFillCanvas(curr_rgba, canvas_width, canvas_height);
      } else {
        DecodedFrame* const prev_frame = &image->frames[i - 1];
        const GifImageDesc* const prev_desc = &gif->SavedImages[i - 1].ImageDesc;
        GraphicsControlBlock prev_gcb;
        memset(&prev_gcb, 0, sizeof(prev_gcb));
        DGifSavedExtensionToGCB(gif, i - 1, &prev_gcb);
  
        curr_frame->is_key_frame =
            IsKeyFrameGIF(prev_desc, prev_gcb.DisposalMode, prev_frame,
                          canvas_width, canvas_height);
  
        if (curr_frame->is_key_frame) {  // Initialize as transparent.
          ZeroFillCanvas(curr_rgba, canvas_width, canvas_height);
        } else {
          int prev_frame_disposed, curr_frame_opaque;
          int prev_frame_completely_covered;
          // Initialize with previous canvas.
          uint8_t* const prev_rgba = image->frames[i - 1].rgba;
          CopyCanvas(prev_rgba, curr_rgba, canvas_width, canvas_height);
  
          // Dispose previous frame rectangle.
          prev_frame_disposed =
              (prev_gcb.DisposalMode == DISPOSE_BACKGROUND ||
               prev_gcb.DisposalMode == DISPOSE_PREVIOUS);
          curr_frame_opaque =
              (curr_gcb.TransparentColor == NO_TRANSPARENT_COLOR);
          prev_frame_completely_covered =
              curr_frame_opaque &&
              CoversFrameGIF(&curr_gif_image->ImageDesc, prev_desc);
  
          if (prev_frame_disposed && !prev_frame_completely_covered) {
            switch (prev_gcb.DisposalMode) {
              case DISPOSE_BACKGROUND: {
                ZeroFillFrameRect(curr_rgba, canvas_width_in_bytes,
                                  prev_desc->Left, prev_desc->Top,
                                  prev_desc->Width, prev_desc->Height);
                break;
              }
              case DISPOSE_PREVIOUS: {
                int src_frame_num = i - 2;
                while (src_frame_num >= 0) {
                  GraphicsControlBlock src_frame_gcb;
                  memset(&src_frame_gcb, 0, sizeof(src_frame_gcb));
                  DGifSavedExtensionToGCB(gif, src_frame_num, &src_frame_gcb);
                  if (src_frame_gcb.DisposalMode != DISPOSE_PREVIOUS) break;
                  --src_frame_num;
                }
                if (src_frame_num >= 0) {
                  // Restore pixels inside previous frame rectangle to
                  // corresponding pixels in source canvas.
                  uint8_t* const src_frame_rgba =
                      image->frames[src_frame_num].rgba;
                  CopyFrameRectangle(src_frame_rgba, curr_rgba,
                                     canvas_width_in_bytes,
                                     prev_desc->Left, prev_desc->Top,
                                     prev_desc->Width, prev_desc->Height);
                } else {
                  // Source canvas doesn't exist. So clear previous frame
                  // rectangle to background.
                  ZeroFillFrameRect(curr_rgba, canvas_width_in_bytes,
                                    prev_desc->Left, prev_desc->Top,
                                    prev_desc->Width, prev_desc->Height);
                }
                break;
              }
              default:
                break;  // Nothing to do.
            }
          }
        }
      }
  
      // Decode current frame.
      if (!ReadFrameGIF(curr_gif_image, gif->SColorMap, curr_gcb.TransparentColor,
                        canvas_width_in_bytes, curr_rgba)) {
        DGifCloseFile(gif, NULL);
        return 0;
      }
  
      if (dump_frames) {
        if (!DumpFrame(filename, dump_folder, i, curr_rgba,
                       canvas_width, canvas_height)) {
          DGifCloseFile(gif, NULL);
          return 0;
        }
      }
    }
    DGifCloseFile(gif, NULL);
    return 1;
  }
  
  #else
  
  static int IsGIF(const WebPData* const data) {
    (void)data;
    return 0;
  }
  
  static int ReadAnimatedGIF(const char filename[], AnimatedImage* const image,
                             int dump_frames, const char dump_folder[]) {
    (void)filename;
    (void)image;
    (void)dump_frames;
    (void)dump_folder;
    fprintf(stderr, "GIF support not compiled. Please install the libgif-dev "
            "package before building.\n");
    return 0;
  }
  
  #endif  // WEBP_HAVE_GIF
  
  // -----------------------------------------------------------------------------
  
  int ReadAnimatedImage(const char filename[], AnimatedImage* const image,
                        int dump_frames, const char dump_folder[]) {
    int ok = 0;
    WebPData webp_data;
  
    WebPDataInit(&webp_data);
    memset(image, 0, sizeof(*image));
  
    if (!ImgIoUtilReadFile(filename, &webp_data.bytes, &webp_data.size)) {
      fprintf(stderr, "Error reading file: %s\n", filename);
      return 0;
    }
  
    if (IsWebP(&webp_data)) {
      ok = ReadAnimatedWebP(filename, &webp_data, image, dump_frames,
                            dump_folder);
    } else if (IsGIF(&webp_data)) {
      ok = ReadAnimatedGIF(filename, image, dump_frames, dump_folder);
    } else {
      fprintf(stderr,
              "Unknown file type: %s. Supported file types are WebP and GIF\n",
              filename);
      ok = 0;
    }
    if (!ok) ClearAnimatedImage(image);
    WebPDataClear(&webp_data);
    return ok;
  }
  
  static void Accumulate(double v1, double v2, double* const max_diff,
                         double* const sse) {
    const double diff = fabs(v1 - v2);
    if (diff > *max_diff) *max_diff = diff;
    *sse += diff * diff;
  }
  
  void GetDiffAndPSNR(const uint8_t rgba1[], const uint8_t rgba2[],
                      uint32_t width, uint32_t height, int premultiply,
                      int* const max_diff, double* const psnr) {
    const uint32_t stride = width * kNumChannels;
    const int kAlphaChannel = kNumChannels - 1;
    double f_max_diff = 0.;
    double sse = 0.;
    uint32_t x, y;
    for (y = 0; y < height; ++y) {
      for (x = 0; x < stride; x += kNumChannels) {
        int k;
        const size_t offset = (size_t)y * stride + x;
        const int alpha1 = rgba1[offset + kAlphaChannel];
        const int alpha2 = rgba2[offset + kAlphaChannel];
        Accumulate(alpha1, alpha2, &f_max_diff, &sse);
        if (!premultiply) {
          for (k = 0; k < kAlphaChannel; ++k) {
            Accumulate(rgba1[offset + k], rgba2[offset + k], &f_max_diff, &sse);
          }
        } else {
          // premultiply R/G/B channels with alpha value
          for (k = 0; k < kAlphaChannel; ++k) {
            Accumulate(rgba1[offset + k] * alpha1 / 255.,
                       rgba2[offset + k] * alpha2 / 255.,
                       &f_max_diff, &sse);
          }
        }
      }
    }
    *max_diff = (int)f_max_diff;
    if (*max_diff == 0) {
      *psnr = 99.;  // PSNR when images are identical.
    } else {
      sse /= stride * height;
      *psnr = 4.3429448 * log(255. * 255. / sse);
    }
  }
  