migrate anim_diff tool from C++ to C89
+ jenkins fixes for native config (library order)
+ add a missing -lm
+ replace log10 by log, just in case
+ partially reverted configure.ac to remove the C++ part
Change-Id: Iee099c544451b23c6cfaca53d5a95d2d332e066e
diff --git a/examples/anim_util.c b/examples/anim_util.c
new file mode 100644
index 0000000..cb83496
--- /dev/null
+++ b/examples/anim_util.c
@@ -0,0 +1,754 @@
+// 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 "./example_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 AllocateFrames(AnimatedImage* const image, uint32_t num_frames) {
+ uint32_t i;
+ const size_t rgba_size =
+ image->canvas_width * kNumChannels * image->canvas_height;
+ uint8_t* const mem = (uint8_t*)malloc(num_frames * rgba_size * sizeof(*mem));
+ DecodedFrame* const frames =
+ (DecodedFrame*)malloc(num_frames * sizeof(*frames));
+
+ 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;
+
+ 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;
+ }
+ for (y = 0; y < canvas_height; ++y) {
+ if (fwrite((const char*)(rgba) + y * canvas_width * kNumChannels,
+ canvas_width * kNumChannels, 1, f) != 1) {
+ fprintf(stderr, "Error writing to file: %s\n", file_name);
+ goto End;
+ }
+ }
+ 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 'file_str' 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);
+ 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, ×tamp)) {
+ fprintf(stderr, "Error decoding frame #%u\n", frame_index);
+ goto End;
+ }
+ 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.
+
+// 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));
+}
+
+#ifdef WEBP_HAVE_GIF
+
+// 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 0x00ffffff; // Special case: transparent white.
+ } 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.
+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) {
+ return ((uint32_t)(eb2->Bytes[2]) << 8) +
+ ((uint32_t)(eb2->Bytes[1]) << 0);
+ }
+ }
+ }
+ return 0; // 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 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 (!ExUtilReadFile(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 = 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);
+ }
+}