go-libwebp

Experimental translation from libwebp to Go source.
Log | Files | Refs | README | LICENSE

anim_util.c (26749B)


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