Blame examples/anim_util.c

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