[gegl] subprojects/libnsgif: update to latest upstream



commit c6ac938c2806fc7240d245e371faa6b40734c63a
Author: Michael Drake <michael drake codethink co uk>
Date:   Tue Mar 15 15:33:43 2022 +0000

    subprojects/libnsgif: update to latest upstream
    
    There has been major changes to libnsgif's API. The most significant
    changes are that everything now has a consistent nsgif_* or NSGIF_*
    namespace (making it suitable for use as a general purpose shared
    library), and the library internal state is no longer exposed to the
    client.
    
    There have also been numerous fixes for exotic GIFs and to align
    behaviour when handling broken GIFs with mainstream browsers.
    
    Upstream: https://git.netsurf-browser.org/libnsgif.git/
    Ref: 61934ff70402ffff91fbea9238cb1833ffa6cec6

 subprojects/libnsgif/COPYING        |    1 +
 subprojects/libnsgif/README         |   36 -
 subprojects/libnsgif/README.md      |  126 +++
 subprojects/libnsgif/gif.c          | 1834 +++++++++++++++++++++++++++++++++++
 subprojects/libnsgif/libnsgif.c     | 1169 ----------------------
 subprojects/libnsgif/libnsgif.h     |  183 ----
 subprojects/libnsgif/libnsgif.pc.in |   10 -
 subprojects/libnsgif/log.h          |   21 -
 subprojects/libnsgif/lzw.c          |  522 +++++++---
 subprojects/libnsgif/lzw.h          |  100 +-
 subprojects/libnsgif/meson.build    |    2 +-
 subprojects/libnsgif/nsgif.h        |  356 +++++++
 12 files changed, 2763 insertions(+), 1597 deletions(-)
---
diff --git a/subprojects/libnsgif/COPYING b/subprojects/libnsgif/COPYING
index c6e1688d7..9334b89b3 100644
--- a/subprojects/libnsgif/COPYING
+++ b/subprojects/libnsgif/COPYING
@@ -1,5 +1,6 @@
 Copyright (C) 2004 Richard Wilson
 Copyright (C) 2008 Sean Fox
+Copyright (C) 2013-2021 Michael Drake
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/subprojects/libnsgif/README.md b/subprojects/libnsgif/README.md
new file mode 100644
index 000000000..63ccdc7f3
--- /dev/null
+++ b/subprojects/libnsgif/README.md
@@ -0,0 +1,126 @@
+LibNSGIF: NetSurf GIF decoder
+=============================
+
+LibNSGIF is a C library for decoding GIF format images and animations.
+It is licenced under the MIT licence.
+
+This library aims to provide a simple API for robust decoding of GIF files.
+
+Details
+-------
+
+The GIF source data is scanned prior to decoding, allowing for efficient
+decoding. The scanning phase will scan currently available data and will
+resume from where it left off when called with additional data.
+
+Only one frame is ever fully decoded to a bitmap at a time, reducing memory
+usage for large GIFs.
+
+Using
+-----
+
+LibNSGIF allows the client to allocate the bitmap into which the GIF is
+decoded. The client can have an arbitrary bitmap structure, that is simply
+a void pointer to LibNSGIF. The client must provide a callback table for
+interacting with bitmaps. This table must include as a minimum functions to
+create and destroy bitmaps, and a function to get a pointer to the bitmap's
+pixel data buffer.
+
+To load a GIF, first create an nsgif object with `nsgif_create()`.
+
+```c
+       err = nsgif_create(&bitmap_callbacks, &gif);
+       if (err != NSGIF_OK) {
+               fprintf(stderr, "%s\n", nsgif_strerror(err));
+               // Handle error
+       }
+```
+
+Now you can load the GIF source data into the nsgif object with
+`nsgif_data_scan()`:
+
+```c
+       err = nsgif_data_scan(gif, size, data);
+       if (err != NSGIF_OK) {
+               fprintf(stderr, "%s\n", nsgif_strerror(err));
+               // Handle error
+       }
+```
+
+This scans the source data and decodes information about each frame, however
+it doesn't decode any of the bitmap data for the frames. The client may call
+`nsgif_data_scan()` multiple times as source data is fetched. The early frames
+can be decoded before the later frames are scanned. Frames have to be scanned
+before they can be decoded.
+
+This function will sometimes return an error. That is OK, and even expected.
+It is fine to proceed to decoding any frames that are available after a scan.
+Some errors indicate that there is a flaw in the source GIF data (not at all
+uncommon, GIF is an ancient format that has had many broken encoders), or that
+it has reached the end of the source data.
+
+> **Note**: The client must not free the data until after calling
+> `nsgif_destroy()`. You can move the data, e.g. if you realloc to a bigger
+> buffer. Just be sure to call `nsgif_data_scan()` again with the new pointer
+> before making any other calls against that nsgif object.
+
+To decode the frames, you can call `nsgif_get_info()` to get the frame_count,
+and then call `nsgif_frame_decode()` for each frame, and manage the animation,
+and non-displayable frames yourself, or you can use the helper function,
+`nsgif_frame_prepare()`:
+
+```c
+       err = nsgif_frame_prepare(gif, &area, &delay_cs, &frame_new);
+       if (err != NSGIF_OK) {
+               fprintf(stderr, "%s\n", nsgif_strerror(err));
+               // Handle error
+       }
+
+       // Update our bitmap to know it should be showing `frame_new` now.
+       // Trigger redraw of `area` of image.
+
+       if (delay_cs != NSGIF_INFINITE) {
+               // Schedule next frame in delay_cs.
+       }
+```
+
+This will return the number of the next frame to be decoded, the delay in cs
+before the next frame should be decoded, and the area of the bitmap that needs
+to be redrawn.
+
+> **Note**: GIF frames may only occupy a portion of the overall bitmap, and only
+> redrawing the area that has changed may be more efficient than redrawing the
+> whole thing. The returned area comprises both any region that has been
+> changed in the disposal of the previous frame and the new frame.
+
+GIF files can limit the number of animation loops to a finite number or they
+may only have one frame. In either of these cases, the returned delay is
+`NSGIF_INFINITE` indicating that the animation is complete. Subsequent calls
+to `nsgif_frame_prepare()` will return `NSGIF_ERR_ANIMATION_END`.
+
+To force the repeat of an animation, call `nsgif_reset()`.
+
+One reason for the two-step decoding of frames is that it enables deferred
+decoding. You can call `nsgif_frame_prepare()` and cause a redraw of that
+portion of your document. If the GIF is off screen (another tab, or scrolled
+out of sight), there is no need to decode it at all.
+
+Once the bitmap is needed for a redraw, you can decode the correct frame
+on-demand with:
+
+```c
+       err = nsgif_frame_decode(gif, frame_new, &bitmap);
+       if (err != NSGIF_OK) {
+               fprintf(stderr, "%s\n", nsgif_strerror(err));
+               // Handle error
+       }
+```
+
+Note that this will be a no-op if the requested frame already happens to be
+the decoded frame.
+
+Once you are done with the GIF, free up the nsgif object with:
+
+```c
+       nsgif_destroy(gif);
+```
diff --git a/subprojects/libnsgif/gif.c b/subprojects/libnsgif/gif.c
new file mode 100644
index 000000000..287b63235
--- /dev/null
+++ b/subprojects/libnsgif/gif.c
@@ -0,0 +1,1834 @@
+/*
+ * Copyright 2004 Richard Wilson <richard wilson netsurf-browser org>
+ * Copyright 2008 Sean Fox <dyntryx gmail com>
+ * Copyright 2013-2022 Michael Drake <tlsa netsurf-browser org>
+ *
+ * This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
+ * Licenced under the MIT License,
+ *                http://www.opensource.org/licenses/mit-license.php
+ */
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+
+#include "lzw.h"
+#include "nsgif.h"
+
+/** Maximum colour table size */
+#define NSGIF_MAX_COLOURS 256
+
+/** GIF frame data */
+typedef struct nsgif_frame {
+       struct nsgif_frame_info info;
+
+       /** offset (in bytes) to the GIF frame data */
+       uint32_t frame_pointer;
+       /** whether the frame has previously been decoded. */
+       bool decoded;
+       /** whether the frame is totally opaque */
+       bool opaque;
+       /** whether a full image redraw is required */
+       bool redraw_required;
+
+       /** the index designating a transparent pixel */
+       uint32_t transparency_index;
+
+       /* Frame flags */
+       uint32_t flags;
+} nsgif_frame;
+
+/** GIF animation data */
+struct nsgif {
+       struct nsgif_info info;
+
+       /** LZW decode context */
+       void *lzw_ctx;
+       /** callbacks for bitmap functions */
+       nsgif_bitmap_cb_vt bitmap;
+       /** decoded frames */
+       nsgif_frame *frames;
+       /** current frame */
+       uint32_t frame;
+       /** current frame decoded to bitmap */
+       uint32_t decoded_frame;
+       /** currently decoded image; stored as bitmap from bitmap_create callback */
+       nsgif_bitmap_t *frame_image;
+
+       uint16_t delay_min;
+       uint16_t delay_default;
+
+       /** number of frames partially decoded */
+       uint32_t frame_count_partial;
+
+       /** pointer to GIF data */
+       const uint8_t *buf;
+       /** current index into GIF data */
+       uint32_t buf_pos;
+       /** total number of bytes of GIF data available */
+       uint32_t buf_len;
+
+       /** current number of frame holders */
+       uint32_t frame_holders;
+       /** background index */
+       uint32_t bg_index;
+       /** image aspect ratio (ignored) */
+       uint32_t aspect_ratio;
+       /** size of colour table (in entries) */
+       uint32_t colour_table_size;
+
+       /** whether the GIF has a global colour table */
+       bool global_colours;
+       /** current colour table */
+       uint32_t *colour_table;
+       /** global colour table */
+       uint32_t global_colour_table[NSGIF_MAX_COLOURS];
+       /** local colour table */
+       uint32_t local_colour_table[NSGIF_MAX_COLOURS];
+
+       /** previous frame for NSGIF_FRAME_RESTORE */
+       void *prev_frame;
+       /** previous frame index */
+       uint32_t prev_index;
+};
+
+/**
+ * Helper macro to get number of elements in an array.
+ *
+ * \param[in]  _a  Array to count elements of.
+ * \return NUlber of elements in array.
+ */
+#define NSGIF_ARRAY_LEN(_a) ((sizeof(_a)) / (sizeof(*_a)))
+
+/**
+ *
+ * \file
+ * \brief GIF image decoder
+ *
+ * The GIF format is thoroughly documented; a full description can be found at
+ * http://www.w3.org/Graphics/GIF/spec-gif89a.txt
+ *
+ * \todo Plain text and comment extensions should be implemented.
+ */
+
+/** Internal flag that the colour table needs to be processed */
+#define NSGIF_PROCESS_COLOURS 0xaa000000
+
+/** Internal flag that a frame is invalid/unprocessed */
+#define NSGIF_FRAME_INVALID UINT32_MAX
+
+/** Transparent colour */
+#define NSGIF_TRANSPARENT_COLOUR 0x00
+
+/** No transparency */
+#define NSGIF_NO_TRANSPARENCY (0xFFFFFFFFu)
+
+/* GIF Flags */
+#define NSGIF_COLOUR_TABLE_MASK 0x80
+#define NSGIF_COLOUR_TABLE_SIZE_MASK 0x07
+#define NSGIF_BLOCK_TERMINATOR 0x00
+#define NSGIF_TRAILER 0x3b
+
+/**
+ * Convert an LZW result code to equivalent GIF result code.
+ *
+ * \param[in]  l_res  LZW response code.
+ * \return GIF result code.
+ */
+static nsgif_error nsgif__error_from_lzw(lzw_result l_res)
+{
+       static const nsgif_error g_res[] = {
+               [LZW_OK]        = NSGIF_OK,
+               [LZW_NO_MEM]    = NSGIF_ERR_OOM,
+               [LZW_OK_EOD]    = NSGIF_ERR_END_OF_DATA,
+               [LZW_NO_DATA]   = NSGIF_ERR_END_OF_DATA,
+               [LZW_EOI_CODE]  = NSGIF_ERR_DATA_FRAME,
+               [LZW_BAD_ICODE] = NSGIF_ERR_DATA_FRAME,
+               [LZW_BAD_CODE]  = NSGIF_ERR_DATA_FRAME,
+       };
+       assert(l_res != LZW_BAD_PARAM);
+       assert(l_res != LZW_NO_COLOUR);
+       return g_res[l_res];
+}
+
+/**
+ * Updates the sprite memory size
+ *
+ * \param gif The animation context
+ * \param width The width of the sprite
+ * \param height The height of the sprite
+ * \return NSGIF_ERR_OOM for a memory error NSGIF_OK for success
+ */
+static nsgif_error nsgif__initialise_sprite(
+               struct nsgif *gif,
+               uint32_t width,
+               uint32_t height)
+{
+       /* Already allocated? */
+       if (gif->frame_image) {
+               return NSGIF_OK;
+       }
+
+       assert(gif->bitmap.create);
+       gif->frame_image = gif->bitmap.create(width, height);
+       if (gif->frame_image == NULL) {
+               return NSGIF_ERR_OOM;
+       }
+
+       return NSGIF_OK;
+}
+
+/**
+ * Helper to get the rendering bitmap for a gif.
+ *
+ * \param[in]  gif  The gif object we're decoding.
+ * \return Client pixel buffer for rendering into.
+ */
+static inline uint32_t* nsgif__bitmap_get(
+               struct nsgif *gif)
+{
+       nsgif_error ret;
+
+       /* Make sure we have a buffer to decode to. */
+       ret = nsgif__initialise_sprite(gif, gif->info.width, gif->info.height);
+       if (ret != NSGIF_OK) {
+               return NULL;
+       }
+
+       /* Get the frame data */
+       assert(gif->bitmap.get_buffer);
+       return (void *)gif->bitmap.get_buffer(gif->frame_image);
+}
+
+/**
+ * Helper to tell the client that their bitmap was modified.
+ *
+ * \param[in]  gif  The gif object we're decoding.
+ */
+static inline void nsgif__bitmap_modified(
+               const struct nsgif *gif)
+{
+       if (gif->bitmap.modified) {
+               gif->bitmap.modified(gif->frame_image);
+       }
+}
+
+/**
+ * Helper to tell the client that whether the bitmap is opaque.
+ *
+ * \param[in]  gif    The gif object we're decoding.
+ * \param[in]  frame  The frame that has been decoded.
+ */
+static inline void nsgif__bitmap_set_opaque(
+               const struct nsgif *gif,
+               const struct nsgif_frame *frame)
+{
+       if (gif->bitmap.set_opaque) {
+               gif->bitmap.set_opaque(
+                               gif->frame_image, frame->opaque);
+       }
+}
+
+/**
+ * Helper to get the client to determine if the bitmap is opaque.
+ *
+ * \todo: We don't really need to get the client to do this for us.
+ *
+ * \param[in]  gif    The gif object we're decoding.
+ * \return true if the bitmap is opaque, false otherwise.
+ */
+static inline bool nsgif__bitmap_get_opaque(
+               const struct nsgif *gif)
+{
+       if (gif->bitmap.test_opaque) {
+               return gif->bitmap.test_opaque(
+                               gif->frame_image);
+       }
+
+       return false;
+}
+
+static void nsgif__record_frame(
+               struct nsgif *gif,
+               const uint32_t *bitmap)
+{
+       uint32_t *prev_frame;
+
+       if (gif->decoded_frame == NSGIF_FRAME_INVALID ||
+           gif->decoded_frame == gif->prev_index) {
+               /* No frame to copy, or already have this frame recorded. */
+               return;
+       }
+
+       bitmap = nsgif__bitmap_get(gif);
+       if (bitmap == NULL) {
+               return;
+       }
+
+       if (gif->prev_frame == NULL) {
+               prev_frame = realloc(gif->prev_frame,
+                               gif->info.width * gif->info.height * 4);
+               if (prev_frame == NULL) {
+                       return;
+               }
+       } else {
+               prev_frame = gif->prev_frame;
+       }
+
+       memcpy(prev_frame, bitmap, gif->info.width * gif->info.height * 4);
+
+       gif->prev_frame  = prev_frame;
+       gif->prev_index  = gif->decoded_frame;
+}
+
+static nsgif_error nsgif__recover_frame(
+               const struct nsgif *gif,
+               uint32_t *bitmap)
+{
+       const uint32_t *prev_frame = gif->prev_frame;
+       unsigned height = gif->info.height;
+       unsigned width  = gif->info.width;
+
+       memcpy(bitmap, prev_frame, height * width * sizeof(*bitmap));
+
+       return NSGIF_OK;
+}
+
+/**
+ * Get the next line for GIF decode.
+ *
+ * Note that the step size must be initialised to 24 at the start of the frame
+ * (when y == 0).  This is because of the first two passes of the frame have
+ * the same step size of 8, and the step size is used to determine the current
+ * pass.
+ *
+ * \param[in]     height     Frame height in pixels.
+ * \param[in,out] y          Current row, starting from 0, updated on exit.
+ * \param[in,out] step       Current step starting with 24, updated on exit.
+ * \return true if there is a row to process, false at the end of the frame.
+ */
+static inline bool nsgif__deinterlace(uint32_t height, uint32_t *y, uint8_t *step)
+{
+       *y += *step & 0xf;
+
+       if (*y < height) return true;
+
+       switch (*step) {
+       case 24: *y = 4; *step = 8; if (*y < height) return true;
+                /* Fall through. */
+       case  8: *y = 2; *step = 4; if (*y < height) return true;
+                /* Fall through. */
+       case  4: *y = 1; *step = 2; if (*y < height) return true;
+                /* Fall through. */
+       default:
+               break;
+       }
+
+       return false;
+}
+
+/**
+ * Get the next line for GIF decode.
+ *
+ * \param[in]     interlace  Non-zero if the frame is not interlaced.
+ * \param[in]     height     Frame height in pixels.
+ * \param[in,out] y          Current row, starting from 0, updated on exit.
+ * \param[in,out] step       Current step starting with 24, updated on exit.
+ * \return true if there is a row to process, false at the end of the frame.
+ */
+static inline bool nsgif__next_row(uint32_t interlace,
+               uint32_t height, uint32_t *y, uint8_t *step)
+{
+       if (!interlace) {
+               return (++*y != height);
+       } else {
+               return nsgif__deinterlace(height, y, step);
+       }
+}
+
+/**
+ * Get any frame clip adjustment for the image extent.
+ *
+ * \param[in]  frame_off  Frame's X or Y offset.
+ * \param[in]  frame_dim  Frame width or height.
+ * \param[in]  image_ext  Image width or height constraint.
+ * \return the amount the frame needs to be clipped to fit the image in given
+ *         dimension.
+ */
+static inline uint32_t gif__clip(
+               uint32_t frame_off,
+               uint32_t frame_dim,
+               uint32_t image_ext)
+{
+       uint32_t frame_ext = frame_off + frame_dim;
+
+       if (frame_ext <= image_ext) {
+               return 0;
+       }
+
+       return frame_ext - image_ext;
+}
+
+/**
+ * Perform any jump over decoded data, to accommodate clipped portion of frame.
+ *
+ * \param[in,out] skip       Number of pixels of data to jump.
+ * \param[in,out] available  Number of pixels of data currently available.
+ * \param[in,out] pos        Position in decoded pixel value data.
+ */
+static inline void gif__jump_data(
+               uint32_t *skip,
+               uint32_t *available,
+               const uint8_t **pos)
+{
+       uint32_t jump = (*skip < *available) ? *skip : *available;
+
+       *skip -= jump;
+       *available -= jump;
+       *pos += jump;
+}
+
+static nsgif_error nsgif__decode_complex(
+               struct nsgif *gif,
+               uint32_t width,
+               uint32_t height,
+               uint32_t offset_x,
+               uint32_t offset_y,
+               uint32_t interlace,
+               const uint8_t *data,
+               uint32_t transparency_index,
+               uint32_t *restrict frame_data,
+               uint32_t *restrict colour_table)
+{
+       lzw_result res;
+       nsgif_error ret = NSGIF_OK;
+       uint32_t clip_x = gif__clip(offset_x, width, gif->info.width);
+       uint32_t clip_y = gif__clip(offset_y, height, gif->info.height);
+       const uint8_t *uncompressed;
+       uint32_t available = 0;
+       uint8_t step = 24;
+       uint32_t skip = 0;
+       uint32_t y = 0;
+
+       if (offset_x >= gif->info.width ||
+           offset_y >= gif->info.height) {
+               return NSGIF_OK;
+       }
+
+       width -= clip_x;
+       height -= clip_y;
+
+       if (width == 0 || height == 0) {
+               return NSGIF_OK;
+       }
+
+       /* Initialise the LZW decoding */
+       res = lzw_decode_init(gif->lzw_ctx, data[0],
+                       gif->buf, gif->buf_len,
+                       data + 1 - gif->buf);
+       if (res != LZW_OK) {
+               return nsgif__error_from_lzw(res);
+       }
+
+       do {
+               uint32_t x;
+               uint32_t *frame_scanline;
+
+               frame_scanline = frame_data + offset_x +
+                               (y + offset_y) * gif->info.width;
+
+               x = width;
+               while (x > 0) {
+                       unsigned row_available;
+                       while (available == 0) {
+                               if (res != LZW_OK) {
+                                       /* Unexpected end of frame, try to recover */
+                                       if (res == LZW_OK_EOD) {
+                                               ret = NSGIF_OK;
+                                       } else {
+                                               ret = nsgif__error_from_lzw(res);
+                                       }
+                                       return ret;
+                               }
+                               res = lzw_decode(gif->lzw_ctx,
+                                               &uncompressed, &available);
+
+                               if (available == 0) {
+                                       return NSGIF_OK;
+                               }
+                               gif__jump_data(&skip, &available, &uncompressed);
+                       }
+
+                       row_available = x < available ? x : available;
+                       x -= row_available;
+                       available -= row_available;
+                       if (transparency_index > 0xFF) {
+                               while (row_available-- > 0) {
+                                       *frame_scanline++ =
+                                               colour_table[*uncompressed++];
+                               }
+                       } else {
+                               while (row_available-- > 0) {
+                                       register uint32_t colour;
+                                       colour = *uncompressed++;
+                                       if (colour != transparency_index) {
+                                               *frame_scanline =
+                                                       colour_table[colour];
+                                       }
+                                       frame_scanline++;
+                               }
+                       }
+               }
+
+               skip = clip_x;
+               gif__jump_data(&skip, &available, &uncompressed);
+       } while (nsgif__next_row(interlace, height, &y, &step));
+
+       return ret;
+}
+
+static nsgif_error nsgif__decode_simple(
+               struct nsgif *gif,
+               uint32_t height,
+               uint32_t offset_y,
+               const uint8_t *data,
+               uint32_t transparency_index,
+               uint32_t *restrict frame_data,
+               uint32_t *restrict colour_table)
+{
+       uint32_t pixels = gif->info.width * height;
+       uint32_t written = 0;
+       nsgif_error ret = NSGIF_OK;
+       lzw_result res;
+
+       if (offset_y >= gif->info.height) {
+               return NSGIF_OK;
+       }
+
+       height -= gif__clip(offset_y, height, gif->info.height);
+
+       if (height == 0) {
+               return NSGIF_OK;
+       }
+
+       /* Initialise the LZW decoding */
+       res = lzw_decode_init_map(gif->lzw_ctx, data[0],
+                       transparency_index, colour_table,
+                       gif->buf, gif->buf_len,
+                       data + 1 - gif->buf);
+       if (res != LZW_OK) {
+               return nsgif__error_from_lzw(res);
+       }
+
+       frame_data += (offset_y * gif->info.width);
+
+       while (pixels > 0) {
+               res = lzw_decode_map(gif->lzw_ctx,
+                               frame_data, pixels, &written);
+               pixels -= written;
+               frame_data += written;
+               if (res != LZW_OK) {
+                       /* Unexpected end of frame, try to recover */
+                       if (res == LZW_OK_EOD) {
+                               ret = NSGIF_OK;
+                       } else {
+                               ret = nsgif__error_from_lzw(res);
+                       }
+                       break;
+               }
+       }
+
+       if (pixels == 0) {
+               ret = NSGIF_OK;
+       }
+
+       return ret;
+}
+
+static inline nsgif_error nsgif__decode(
+               struct nsgif *gif,
+               struct nsgif_frame *frame,
+               const uint8_t *data,
+               uint32_t *restrict frame_data)
+{
+       enum {
+               GIF_MASK_INTERLACE = 0x40,
+       };
+
+       nsgif_error ret;
+       uint32_t width  = frame->info.rect.x1 - frame->info.rect.x0;
+       uint32_t height = frame->info.rect.y1 - frame->info.rect.y0;
+       uint32_t offset_x = frame->info.rect.x0;
+       uint32_t offset_y = frame->info.rect.y0;
+       uint32_t interlace = frame->flags & GIF_MASK_INTERLACE;
+       uint32_t transparency_index = frame->transparency_index;
+       uint32_t *restrict colour_table = gif->colour_table;
+
+       if (interlace == false && width == gif->info.width && offset_x == 0) {
+               ret = nsgif__decode_simple(gif, height, offset_y,
+                               data, transparency_index,
+                               frame_data, colour_table);
+       } else {
+               ret = nsgif__decode_complex(gif, width, height,
+                               offset_x, offset_y, interlace,
+                               data, transparency_index,
+                               frame_data, colour_table);
+       }
+
+       return ret;
+}
+
+/**
+ * Helper to assign a pixel representation from a gif background colour array.
+ *
+ * \param[in]  bg  The background colour to read from.
+ * \param[out] px  The pixel colour to write.
+ */
+static inline void nsgif__gif_bg_to_px(
+               const uint8_t bg[4], uint32_t *px)
+{
+       *px = *(uint32_t *)bg;
+}
+
+/**
+ * Helper to assign a gif background colour array from a pixel representation.
+ *
+ * \param[in]  px  The pixel colour to read from.
+ * \param[out] bg  The background colour to write.
+ */
+static inline void nsgif__gif_px_to_bg(
+               const uint32_t *px, uint8_t bg[4])
+{
+       *(uint32_t *)bg = *px;
+}
+
+/**
+ * Restore a GIF to the background colour.
+ *
+ * \param[in] gif     The gif object we're decoding.
+ * \param[in] frame   The frame to clear, or NULL.
+ * \param[in] bitmap  The bitmap to clear the frame in.
+ */
+static void nsgif__restore_bg(
+               struct nsgif *gif,
+               struct nsgif_frame *frame,
+               uint32_t *bitmap)
+{
+       if (frame == NULL) {
+               memset(bitmap, NSGIF_TRANSPARENT_COLOUR,
+                               gif->info.width * gif->info.height * sizeof(*bitmap));
+       } else {
+               uint32_t width  = frame->info.rect.x1 - frame->info.rect.x0;
+               uint32_t height = frame->info.rect.y1 - frame->info.rect.y0;
+               uint32_t offset_x = frame->info.rect.x0;
+               uint32_t offset_y = frame->info.rect.y0;
+
+               if (frame->info.display == false ||
+                   frame->info.rect.x0 >= gif->info.width ||
+                   frame->info.rect.y0 >= gif->info.height) {
+                       return;
+               }
+
+               width -= gif__clip(offset_x, width, gif->info.width);
+               height -= gif__clip(offset_y, height, gif->info.height);
+
+               if (frame->info.transparency) {
+                       for (uint32_t y = 0; y < height; y++) {
+                               uint32_t *scanline = bitmap + offset_x +
+                                               (offset_y + y) * gif->info.width;
+                               memset(scanline, NSGIF_TRANSPARENT_COLOUR,
+                                               width * sizeof(*bitmap));
+                       }
+               } else {
+                       for (uint32_t y = 0; y < height; y++) {
+                               uint32_t *scanline = bitmap + offset_x +
+                                               (offset_y + y) * gif->info.width;
+                               for (uint32_t x = 0; x < width; x++) {
+                                       nsgif__gif_bg_to_px(
+                                                       gif->info.background,
+                                                       &scanline[x]);
+                               }
+                       }
+               }
+       }
+}
+
+static nsgif_error nsgif__update_bitmap(
+               struct nsgif *gif,
+               struct nsgif_frame *frame,
+               const uint8_t *data,
+               uint32_t frame_idx)
+{
+       nsgif_error ret;
+       uint32_t *bitmap;
+
+       gif->decoded_frame = frame_idx;
+
+       bitmap = nsgif__bitmap_get(gif);
+       if (bitmap == NULL) {
+               return NSGIF_ERR_OOM;
+       }
+
+       /* Handle any bitmap clearing/restoration required before decoding this
+        * frame. */
+       if (frame_idx == 0 || gif->decoded_frame == NSGIF_FRAME_INVALID) {
+               nsgif__restore_bg(gif, NULL, bitmap);
+
+       } else {
+               struct nsgif_frame *prev = &gif->frames[frame_idx - 1];
+
+               if (prev->info.disposal == NSGIF_DISPOSAL_RESTORE_BG) {
+                       nsgif__restore_bg(gif, prev, bitmap);
+
+               } else if (prev->info.disposal == NSGIF_DISPOSAL_RESTORE_PREV) {
+                       ret = nsgif__recover_frame(gif, bitmap);
+                       if (ret != NSGIF_OK) {
+                               nsgif__restore_bg(gif, prev, bitmap);
+                       }
+               }
+       }
+
+       if (frame->info.disposal == NSGIF_DISPOSAL_RESTORE_PREV) {
+               /* Store the previous frame for later restoration */
+               nsgif__record_frame(gif, bitmap);
+       }
+
+       ret = nsgif__decode(gif, frame, data, bitmap);
+
+       nsgif__bitmap_modified(gif);
+
+       if (!frame->decoded) {
+               frame->opaque = nsgif__bitmap_get_opaque(gif);
+               frame->decoded = true;
+       }
+       nsgif__bitmap_set_opaque(gif, frame);
+
+       return ret;
+}
+
+/**
+ * Parse the graphic control extension
+ *
+ * \param[in] frame  The gif frame object we're decoding.
+ * \param[in] data   The data to decode.
+ * \param[in] len    Byte length of data.
+ * \return NSGIF_ERR_END_OF_DATA if more data is needed,
+ *         NSGIF_OK for success.
+ */
+static nsgif_error nsgif__parse_extension_graphic_control(
+               struct nsgif_frame *frame,
+               const uint8_t *data,
+               size_t len)
+{
+       enum {
+               GIF_MASK_TRANSPARENCY = 0x01,
+               GIF_MASK_DISPOSAL     = 0x1c,
+       };
+
+       /* 6-byte Graphic Control Extension is:
+        *
+        *  +0  CHAR    Graphic Control Label
+        *  +1  CHAR    Block Size
+        *  +2  CHAR    __Packed Fields__
+        *              3BITS   Reserved
+        *              3BITS   Disposal Method
+        *              1BIT    User Input Flag
+        *              1BIT    Transparent Color Flag
+        *  +3  SHORT   Delay Time
+        *  +5  CHAR    Transparent Color Index
+        */
+       if (len < 6) {
+               return NSGIF_ERR_END_OF_DATA;
+       }
+
+       frame->info.delay = data[3] | (data[4] << 8);
+
+       if (data[2] & GIF_MASK_TRANSPARENCY) {
+               frame->info.transparency = true;
+               frame->transparency_index = data[5];
+       }
+
+       frame->info.disposal = ((data[2] & GIF_MASK_DISPOSAL) >> 2);
+       /* I have encountered documentation and GIFs in the
+        * wild that use 0x04 to restore the previous frame,
+        * rather than the officially documented 0x03.  I
+        * believe some (older?)  software may even actually
+        * export this way.  We handle this as a type of
+        * "quirks" mode. */
+       if (frame->info.disposal == NSGIF_DISPOSAL_RESTORE_QUIRK) {
+               frame->info.disposal = NSGIF_DISPOSAL_RESTORE_PREV;
+       }
+
+       /* if we are clearing the background then we need to
+        * redraw enough to cover the previous frame too. */
+       frame->redraw_required =
+                       frame->info.disposal == NSGIF_DISPOSAL_RESTORE_BG ||
+                       frame->info.disposal == NSGIF_DISPOSAL_RESTORE_PREV;
+
+       return NSGIF_OK;
+}
+
+/**
+ * Check an app ext identifier and authentication code for loop count extension.
+ *
+ * \param[in] data  The data to decode.
+ * \param[in] len   Byte length of data.
+ * \return true if extension is a loop count extension.
+ */
+static bool nsgif__app_ext_is_loop_count(
+               const uint8_t *data,
+               size_t len)
+{
+       enum {
+               EXT_LOOP_COUNT_BLOCK_SIZE = 0x0b,
+       };
+
+       assert(len > 13);
+       (void)(len);
+
+       if (data[1] == EXT_LOOP_COUNT_BLOCK_SIZE) {
+               if (strncmp((const char *)data + 2, "NETSCAPE2.0", 11) == 0 ||
+                   strncmp((const char *)data + 2, "ANIMEXTS1.0", 11) == 0) {
+                       return true;
+               }
+       }
+
+       return false;
+}
+
+/**
+ * Parse the application extension
+ *
+ * \param[in] gif   The gif object we're decoding.
+ * \param[in] data  The data to decode.
+ * \param[in] len   Byte length of data.
+ * \return NSGIF_ERR_END_OF_DATA if more data is needed,
+ *         NSGIF_OK for success.
+ */
+static nsgif_error nsgif__parse_extension_application(
+               struct nsgif *gif,
+               const uint8_t *data,
+               size_t len)
+{
+       /* 14-byte+ Application Extension is:
+        *
+        *  +0    CHAR    Application Extension Label
+        *  +1    CHAR    Block Size
+        *  +2    8CHARS  Application Identifier
+        *  +10   3CHARS  Appl. Authentication Code
+        *  +13   1-256   Application Data (Data sub-blocks)
+        */
+       if (len < 17) {
+               return NSGIF_ERR_END_OF_DATA;
+       }
+
+       if (nsgif__app_ext_is_loop_count(data, len)) {
+               enum {
+                       EXT_LOOP_COUNT_SUB_BLOCK_SIZE = 0x03,
+                       EXT_LOOP_COUNT_SUB_BLOCK_ID   = 0x01,
+               };
+               if ((data[13] == EXT_LOOP_COUNT_SUB_BLOCK_SIZE) &&
+                   (data[14] == EXT_LOOP_COUNT_SUB_BLOCK_ID)) {
+                       gif->info.loop_max = data[15] | (data[16] << 8);
+
+                       /* The value in the source data means repeat N times
+                        * after the first implied play. A value of zero has
+                        * the special meaning of loop forever. (The only way
+                        * to play the animation once is  not to have this
+                        * extension at all. */
+                       if (gif->info.loop_max > 0) {
+                               gif->info.loop_max++;
+                       }
+               }
+       }
+
+       return NSGIF_OK;
+}
+
+/**
+ * Parse the frame's extensions
+ *
+ * \param[in] gif     The gif object we're decoding.
+ * \param[in] frame   The frame to parse extensions for.
+ * \param[in] pos     Current position in data, updated on exit.
+ * \param[in] decode  Whether to decode or skip over the extension.
+ * \return NSGIF_ERR_END_OF_DATA if more data is needed,
+ *         NSGIF_OK for success.
+ */
+static nsgif_error nsgif__parse_frame_extensions(
+               struct nsgif *gif,
+               struct nsgif_frame *frame,
+               const uint8_t **pos,
+               bool decode)
+{
+       enum {
+               GIF_EXT_INTRODUCER      = 0x21,
+               GIF_EXT_GRAPHIC_CONTROL = 0xf9,
+               GIF_EXT_COMMENT         = 0xfe,
+               GIF_EXT_PLAIN_TEXT      = 0x01,
+               GIF_EXT_APPLICATION     = 0xff,
+       };
+       const uint8_t *nsgif_data = *pos;
+       const uint8_t *nsgif_end = gif->buf + gif->buf_len;
+       int nsgif_bytes = nsgif_end - nsgif_data;
+
+       /* Initialise the extensions */
+       while (nsgif_bytes > 0 && nsgif_data[0] == GIF_EXT_INTRODUCER) {
+               bool block_step = true;
+               nsgif_error ret;
+
+               nsgif_data++;
+               nsgif_bytes--;
+
+               if (nsgif_bytes == 0) {
+                       return NSGIF_ERR_END_OF_DATA;
+               }
+
+               /* Switch on extension label */
+               switch (nsgif_data[0]) {
+               case GIF_EXT_GRAPHIC_CONTROL:
+                       if (decode) {
+                               ret = nsgif__parse_extension_graphic_control(
+                                               frame,
+                                               nsgif_data,
+                                               nsgif_bytes);
+                               if (ret != NSGIF_OK) {
+                                       return ret;
+                               }
+                       }
+                       break;
+
+               case GIF_EXT_APPLICATION:
+                       if (decode) {
+                               ret = nsgif__parse_extension_application(
+                                               gif, nsgif_data, nsgif_bytes);
+                               if (ret != NSGIF_OK) {
+                                       return ret;
+                               }
+                       }
+                       break;
+
+               case GIF_EXT_COMMENT:
+                       /* Move the pointer to the first data sub-block Skip 1
+                        * byte for the extension label. */
+                       ++nsgif_data;
+                       block_step = false;
+                       break;
+
+               default:
+                       break;
+               }
+
+               if (block_step) {
+                       /* Move the pointer to the first data sub-block Skip 2
+                        * bytes for the extension label and size fields Skip
+                        * the extension size itself
+                        */
+                       if (nsgif_bytes < 2) {
+                               return NSGIF_ERR_END_OF_DATA;
+                       }
+                       nsgif_data += 2 + nsgif_data[1];
+               }
+
+               /* Repeatedly skip blocks until we get a zero block or run out
+                * of data.  This data is ignored by this gif decoder. */
+               while (nsgif_data < nsgif_end && nsgif_data[0] != NSGIF_BLOCK_TERMINATOR) {
+                       nsgif_data += nsgif_data[0] + 1;
+                       if (nsgif_data >= nsgif_end) {
+                               return NSGIF_ERR_END_OF_DATA;
+                       }
+               }
+               nsgif_data++;
+               nsgif_bytes = nsgif_end - nsgif_data;
+       }
+
+       if (nsgif_data > nsgif_end) {
+               nsgif_data = nsgif_end;
+       }
+
+       /* Set buffer position and return */
+       *pos = nsgif_data;
+       return NSGIF_OK;
+}
+
+/**
+ * Parse a GIF Image Descriptor.
+ *
+ * The format is:
+ *
+ *  +0   CHAR   Image Separator (0x2c)
+ *  +1   SHORT  Image Left Position
+ *  +3   SHORT  Image Top Position
+ *  +5   SHORT  Width
+ *  +7   SHORT  Height
+ *  +9   CHAR   __Packed Fields__
+ *              1BIT    Local Colour Table Flag
+ *              1BIT    Interlace Flag
+ *              1BIT    Sort Flag
+ *              2BITS   Reserved
+ *              3BITS   Size of Local Colour Table
+ *
+ * \param[in] gif     The gif object we're decoding.
+ * \param[in] frame   The frame to parse an image descriptor for.
+ * \param[in] pos     Current position in data, updated on exit.
+ * \param[in] decode  Whether to decode the image descriptor.
+ * \return NSGIF_OK on success, appropriate error otherwise.
+ */
+static nsgif_error nsgif__parse_image_descriptor(
+               struct nsgif *gif,
+               struct nsgif_frame *frame,
+               const uint8_t **pos,
+               bool decode)
+{
+       const uint8_t *data = *pos;
+       size_t len = gif->buf + gif->buf_len - data;
+       enum {
+               NSGIF_IMAGE_DESCRIPTOR_LEN = 10u,
+               NSGIF_IMAGE_SEPARATOR      = 0x2Cu,
+       };
+
+       assert(gif != NULL);
+       assert(frame != NULL);
+
+       if (len < NSGIF_IMAGE_DESCRIPTOR_LEN) {
+               return NSGIF_ERR_END_OF_DATA;
+       }
+
+       if (decode) {
+               uint32_t x, y, w, h;
+
+               if (data[0] != NSGIF_IMAGE_SEPARATOR) {
+                       return NSGIF_ERR_DATA_FRAME;
+               }
+
+               x = data[1] | (data[2] << 8);
+               y = data[3] | (data[4] << 8);
+               w = data[5] | (data[6] << 8);
+               h = data[7] | (data[8] << 8);
+               frame->flags = data[9];
+
+               frame->info.rect.x0 = x;
+               frame->info.rect.y0 = y;
+               frame->info.rect.x1 = x + w;
+               frame->info.rect.y1 = y + h;
+
+               /* Allow first frame to grow image dimensions. */
+               if (gif->info.frame_count == 0) {
+                       if (x + w > gif->info.width) {
+                               gif->info.width = x + w;
+                       }
+                       if (y + h > gif->info.height) {
+                               gif->info.height = y + h;
+                       }
+               }
+       }
+
+       *pos += NSGIF_IMAGE_DESCRIPTOR_LEN;
+       return NSGIF_OK;
+}
+
+/**
+ * Extract a GIF colour table into a LibNSGIF colour table buffer.
+ *
+ * \param[in] gif                   The gif object we're decoding.
+ * \param[in] colour_table          The colour table to populate.
+ * \param[in] colour_table_entries  The number of colour table entries.
+ * \param[in] pos                   Current position in data, updated on exit.
+ * \param[in] decode                Whether to decode the colour table.
+ * \return NSGIF_OK on success, appropriate error otherwise.
+ */
+static nsgif_error nsgif__colour_table_extract(
+               struct nsgif *gif,
+               uint32_t colour_table[NSGIF_MAX_COLOURS],
+               size_t colour_table_entries,
+               const uint8_t **pos,
+               bool decode)
+{
+       const uint8_t *data = *pos;
+       size_t len = gif->buf + gif->buf_len - data;
+
+       if (len < colour_table_entries * 3) {
+               return NSGIF_ERR_END_OF_DATA;
+       }
+
+       if (decode) {
+               int count = colour_table_entries;
+               uint8_t *entry = (uint8_t *)colour_table;
+
+               while (count--) {
+                       /* Gif colour map contents are r,g,b.
+                        *
+                        * We want to pack them bytewise into the
+                        * colour table, such that the red component
+                        * is in byte 0 and the alpha component is in
+                        * byte 3.
+                        */
+
+                       *entry++ = *data++; /* r */
+                       *entry++ = *data++; /* g */
+                       *entry++ = *data++; /* b */
+                       *entry++ = 0xff;    /* a */
+               }
+       }
+
+       *pos += colour_table_entries * 3;
+       return NSGIF_OK;
+}
+
+/**
+ * Get a frame's colour table.
+ *
+ * Sets up gif->colour_table for the frame.
+ *
+ * \param[in] gif     The gif object we're decoding.
+ * \param[in] frame   The frame to get the colour table for.
+ * \param[in] pos     Current position in data, updated on exit.
+ * \param[in] decode  Whether to decode the colour table.
+ * \return NSGIF_OK on success, appropriate error otherwise.
+ */
+static nsgif_error nsgif__parse_colour_table(
+               struct nsgif *gif,
+               struct nsgif_frame *frame,
+               const uint8_t **pos,
+               bool decode)
+{
+       nsgif_error ret;
+
+       assert(gif != NULL);
+       assert(frame != NULL);
+
+       if ((frame->flags & NSGIF_COLOUR_TABLE_MASK) == 0) {
+               gif->colour_table = gif->global_colour_table;
+               return NSGIF_OK;
+       }
+
+       ret = nsgif__colour_table_extract(gif, gif->local_colour_table,
+                       2 << (frame->flags & NSGIF_COLOUR_TABLE_SIZE_MASK),
+                       pos, decode);
+       if (ret != NSGIF_OK) {
+               return ret;
+       }
+
+       gif->colour_table = gif->local_colour_table;
+       return NSGIF_OK;
+}
+
+/**
+ * Parse the image data for a gif frame.
+ *
+ * Sets up gif->colour_table for the frame.
+ *
+ * \param[in] gif     The gif object we're decoding.
+ * \param[in] frame   The frame to parse image data for.
+ * \param[in] pos     Current position in data, updated on exit.
+ * \param[in] decode  Whether to decode the image data.
+ * \return NSGIF_OK on success, appropriate error otherwise.
+ */
+static nsgif_error nsgif__parse_image_data(
+               struct nsgif *gif,
+               struct nsgif_frame *frame,
+               const uint8_t **pos,
+               bool decode)
+{
+       const uint8_t *data = *pos;
+       size_t len = gif->buf + gif->buf_len - data;
+       uint32_t frame_idx = frame - gif->frames;
+       uint8_t minimum_code_size;
+       nsgif_error ret;
+
+       assert(gif != NULL);
+       assert(frame != NULL);
+
+       if (!decode) {
+               gif->frame_count_partial = frame_idx + 1;
+       }
+
+       /* Ensure sufficient data remains.  A gif trailer or a minimum lzw code
+        * followed by a gif trailer is treated as OK, although without any
+        * image data. */
+       switch (len) {
+               default: if (data[0] == NSGIF_TRAILER) return NSGIF_OK;
+                       break;
+               case 2: if (data[1] == NSGIF_TRAILER) return NSGIF_OK;
+                       /* Fall through. */
+               case 1: if (data[0] == NSGIF_TRAILER) return NSGIF_OK;
+                       /* Fall through. */
+               case 0: return NSGIF_ERR_END_OF_DATA;
+       }
+
+       minimum_code_size = data[0];
+       if (minimum_code_size >= LZW_CODE_MAX) {
+               return NSGIF_ERR_DATA_FRAME;
+       }
+
+       if (decode) {
+               ret = nsgif__update_bitmap(gif, frame, data, frame_idx);
+       } else {
+               uint32_t block_size = 0;
+
+               /* Skip the minimum code size. */
+               data++;
+               len--;
+
+               while (block_size != 1) {
+                       if (len < 1) return NSGIF_ERR_END_OF_DATA;
+                       block_size = data[0] + 1;
+                       /* Check if the frame data runs off the end of the file */
+                       if (block_size > len) {
+                               block_size = len;
+                               return NSGIF_OK;
+                       }
+
+                       len -= block_size;
+                       data += block_size;
+               }
+
+               *pos = data;
+
+               gif->info.frame_count = frame_idx + 1;
+               gif->frames[frame_idx].info.display = true;
+
+               return NSGIF_OK;
+       }
+
+       return ret;
+}
+
+static struct nsgif_frame *nsgif__get_frame(
+               struct nsgif *gif,
+               uint32_t frame_idx)
+{
+       struct nsgif_frame *frame;
+
+       if (gif->frame_holders > frame_idx) {
+               frame = &gif->frames[frame_idx];
+       } else {
+               /* Allocate more memory */
+               size_t count = frame_idx + 1;
+               struct nsgif_frame *temp;
+
+               temp = realloc(gif->frames, count * sizeof(*frame));
+               if (temp == NULL) {
+                       return NULL;
+               }
+               gif->frames = temp;
+               gif->frame_holders = count;
+
+               frame = &gif->frames[frame_idx];
+
+               frame->transparency_index = NSGIF_NO_TRANSPARENCY;
+               frame->frame_pointer = gif->buf_pos;
+               frame->info.transparency = false;
+               frame->redraw_required = false;
+               frame->info.display = false;
+               frame->info.disposal = 0;
+               frame->info.delay = 10;
+               frame->decoded = false;
+       }
+
+       return frame;
+}
+
+/**
+ * Attempts to initialise the next frame
+ *
+ * \param[in] gif       The animation context
+ * \param[in] frame_idx The frame number to decode.
+ * \param[in] decode    Whether to decode the graphical image data.
+ * \return NSGIF_OK on success, appropriate error otherwise.
+*/
+static nsgif_error nsgif__process_frame(
+               struct nsgif *gif,
+               uint32_t frame_idx,
+               bool decode)
+{
+       nsgif_error ret;
+       const uint8_t *pos;
+       const uint8_t *end;
+       struct nsgif_frame *frame;
+
+       frame = nsgif__get_frame(gif, frame_idx);
+       if (frame == NULL) {
+               return NSGIF_ERR_OOM;
+       }
+
+       end = gif->buf + gif->buf_len;
+
+       if (decode) {
+               pos = gif->buf + frame->frame_pointer;
+
+               /* Ensure this frame is supposed to be decoded */
+               if (frame->info.display == false) {
+                       return NSGIF_OK;
+               }
+
+               /* Ensure the frame is in range to decode */
+               if (frame_idx > gif->frame_count_partial) {
+                       return NSGIF_ERR_END_OF_DATA;
+               }
+
+               /* Done if frame is already decoded */
+               if (frame_idx == gif->decoded_frame) {
+                       return NSGIF_OK;
+               }
+       } else {
+               pos = (uint8_t *)(gif->buf + gif->buf_pos);
+
+               /* Check if we've finished */
+               if (pos < end && pos[0] == NSGIF_TRAILER) {
+                       return NSGIF_OK;
+               }
+
+               /* We could theoretically get some junk data that gives us
+                * millions of frames, so we ensure that we don't have a
+                * silly number. */
+               if (frame_idx > 4096) {
+                       return NSGIF_ERR_FRAME_COUNT;
+               }
+       }
+
+       ret = nsgif__parse_frame_extensions(gif, frame, &pos, !decode);
+       if (ret != NSGIF_OK) {
+               goto cleanup;
+       }
+
+       ret = nsgif__parse_image_descriptor(gif, frame, &pos, !decode);
+       if (ret != NSGIF_OK) {
+               goto cleanup;
+       }
+
+       ret = nsgif__parse_colour_table(gif, frame, &pos, decode);
+       if (ret != NSGIF_OK) {
+               goto cleanup;
+       }
+
+       ret = nsgif__parse_image_data(gif, frame, &pos, decode);
+       if (ret != NSGIF_OK) {
+               goto cleanup;
+       }
+
+cleanup:
+       if (!decode) {
+               gif->buf_pos = pos - gif->buf;
+       }
+
+       return ret;
+}
+
+/* exported function documented in nsgif.h */
+void nsgif_destroy(nsgif_t *gif)
+{
+       if (gif == NULL) {
+               return;
+       }
+
+       /* Release all our memory blocks */
+       if (gif->frame_image) {
+               assert(gif->bitmap.destroy);
+               gif->bitmap.destroy(gif->frame_image);
+               gif->frame_image = NULL;
+       }
+
+       free(gif->frames);
+       gif->frames = NULL;
+
+       free(gif->prev_frame);
+       gif->prev_frame = NULL;
+
+       lzw_context_destroy(gif->lzw_ctx);
+       gif->lzw_ctx = NULL;
+
+       free(gif);
+}
+
+/* exported function documented in nsgif.h */
+nsgif_error nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif_t **gif_out)
+{
+       nsgif_t *gif;
+
+       gif = calloc(1, sizeof(*gif));
+       if (gif == NULL) {
+               return NSGIF_ERR_OOM;
+       }
+
+       gif->bitmap = *bitmap_vt;
+       gif->decoded_frame = NSGIF_FRAME_INVALID;
+       gif->prev_index = NSGIF_FRAME_INVALID;
+
+       gif->delay_min = 2;
+       gif->delay_default = 10;
+
+       *gif_out = gif;
+       return NSGIF_OK;
+}
+
+/**
+ * Read GIF header.
+ *
+ * 6-byte GIF file header is:
+ *
+ *  +0   3CHARS   Signature ('GIF')
+ *  +3   3CHARS   Version ('87a' or '89a')
+ *
+ * \param[in]      gif     The GIF object we're decoding.
+ * \param[in,out]  pos     The current buffer position, updated on success.
+ * \param[in]      strict  Whether to require a known GIF version.
+ * \return NSGIF_OK on success, appropriate error otherwise.
+ */
+static nsgif_error nsgif__parse_header(
+               struct nsgif *gif,
+               const uint8_t **pos,
+               bool strict)
+{
+       const uint8_t *data = *pos;
+       size_t len = gif->buf + gif->buf_len - data;
+
+       if (len < 6) {
+               return NSGIF_ERR_END_OF_DATA;
+       }
+
+       if (strncmp((const char *) data, "GIF", 3) != 0) {
+               return NSGIF_ERR_DATA;
+       }
+       data += 3;
+
+       if (strict == true) {
+               if ((strncmp((const char *) data, "87a", 3) != 0) &&
+                   (strncmp((const char *) data, "89a", 3) != 0)) {
+                       return NSGIF_ERR_DATA;
+               }
+       }
+       data += 3;
+
+       *pos = data;
+       return NSGIF_OK;
+}
+
+/**
+ * Read Logical Screen Descriptor.
+ *
+ * 7-byte Logical Screen Descriptor is:
+ *
+ *  +0   SHORT   Logical Screen Width
+ *  +2   SHORT   Logical Screen Height
+ *  +4   CHAR    __Packed Fields__
+ *               1BIT    Global Colour Table Flag
+ *               3BITS   Colour Resolution
+ *               1BIT    Sort Flag
+ *               3BITS   Size of Global Colour Table
+ *  +5   CHAR    Background Colour Index
+ *  +6   CHAR    Pixel Aspect Ratio
+ *
+ * \param[in]      gif     The GIF object we're decoding.
+ * \param[in,out]  pos     The current buffer position, updated on success.
+ * \return NSGIF_OK on success, appropriate error otherwise.
+ */
+static nsgif_error nsgif__parse_logical_screen_descriptor(
+               struct nsgif *gif,
+               const uint8_t **pos)
+{
+       const uint8_t *data = *pos;
+       size_t len = gif->buf + gif->buf_len - data;
+
+       if (len < 7) {
+               return NSGIF_ERR_END_OF_DATA;
+       }
+
+       gif->info.width = data[0] | (data[1] << 8);
+       gif->info.height = data[2] | (data[3] << 8);
+       gif->global_colours = data[4] & NSGIF_COLOUR_TABLE_MASK;
+       gif->colour_table_size = 2 << (data[4] & NSGIF_COLOUR_TABLE_SIZE_MASK);
+       gif->bg_index = data[5];
+       gif->aspect_ratio = data[6];
+       gif->info.loop_max = 1;
+
+       *pos += 7;
+       return NSGIF_OK;
+}
+
+/* exported function documented in nsgif.h */
+nsgif_error nsgif_data_scan(
+               nsgif_t *gif,
+               size_t size,
+               const uint8_t *data)
+{
+       const uint8_t *nsgif_data;
+       nsgif_error ret;
+       uint32_t frames;
+
+       /* Initialize values */
+       gif->buf_len = size;
+       gif->buf = data;
+
+       /* Get our current processing position */
+       nsgif_data = gif->buf + gif->buf_pos;
+
+       /* See if we should initialise the GIF */
+       if (gif->buf_pos == 0) {
+               /* We want everything to be NULL before we start so we've no
+                * chance of freeing bad pointers (paranoia)
+                */
+               gif->frame_image = NULL;
+               gif->frames = NULL;
+               gif->frame_holders = 0;
+
+               /* The caller may have been lazy and not reset any values */
+               gif->info.frame_count = 0;
+               gif->frame_count_partial = 0;
+               gif->decoded_frame = NSGIF_FRAME_INVALID;
+               gif->frame = NSGIF_FRAME_INVALID;
+
+               ret = nsgif__parse_header(gif, &nsgif_data, false);
+               if (ret != NSGIF_OK) {
+                       return ret;
+               }
+
+               ret = nsgif__parse_logical_screen_descriptor(gif, &nsgif_data);
+               if (ret != NSGIF_OK) {
+                       return ret;
+               }
+
+               /* Remember we've done this now */
+               gif->buf_pos = nsgif_data - gif->buf;
+
+               /* Some broken GIFs report the size as the screen size they
+                * were created in. As such, we detect for the common cases and
+                * set the sizes as 0 if they are found which results in the
+                * GIF being the maximum size of the frames.
+                */
+               if (((gif->info.width == 640) && (gif->info.height == 480)) ||
+                   ((gif->info.width == 640) && (gif->info.height == 512)) ||
+                   ((gif->info.width == 800) && (gif->info.height == 600)) ||
+                   ((gif->info.width == 1024) && (gif->info.height == 768)) ||
+                   ((gif->info.width == 1280) && (gif->info.height == 1024)) ||
+                   ((gif->info.width == 1600) && (gif->info.height == 1200)) ||
+                   ((gif->info.width == 0) || (gif->info.height == 0)) ||
+                   ((gif->info.width > 2048) || (gif->info.height > 2048))) {
+                       gif->info.width = 1;
+                       gif->info.height = 1;
+               }
+
+               /* Set the first colour to a value that will never occur in
+                * reality so we know if we've processed it
+               */
+               gif->global_colour_table[0] = NSGIF_PROCESS_COLOURS;
+
+               /* Check if the GIF has no frame data (13-byte header + 1-byte
+                * termination block) Although generally useless, the GIF
+                * specification does not expressly prohibit this
+                */
+               if (gif->buf_len == gif->buf_pos + 1) {
+                       if (nsgif_data[0] == NSGIF_TRAILER) {
+                               return NSGIF_OK;
+                       }
+               }
+       }
+
+       /*  Do the colour map if we haven't already. As the top byte is always
+        *  0xff or 0x00 depending on the transparency we know if it's been
+        *  filled in.
+        */
+       if (gif->global_colour_table[0] == NSGIF_PROCESS_COLOURS) {
+               /* Check for a global colour map signified by bit 7 */
+               if (gif->global_colours) {
+                       ret = nsgif__colour_table_extract(gif,
+                                       gif->global_colour_table,
+                                       gif->colour_table_size,
+                                       &nsgif_data, true);
+                       if (ret != NSGIF_OK) {
+                               return ret;
+                       }
+
+                       gif->buf_pos = (nsgif_data - gif->buf);
+               } else {
+                       /* Create a default colour table with the first two
+                        * colours as black and white
+                        */
+                       uint32_t *entry = gif->global_colour_table;
+
+                       entry[0] = 0x00000000;
+                       /* Force Alpha channel to opaque */
+                       ((uint8_t *) entry)[3] = 0xff;
+
+                       entry[1] = 0xffffffff;
+               }
+
+               if (gif->global_colours &&
+                   gif->bg_index < gif->colour_table_size) {
+                       size_t bg_idx = gif->bg_index;
+                       nsgif__gif_px_to_bg(
+                                       &gif->global_colour_table[bg_idx],
+                                       gif->info.background);
+               } else {
+                       nsgif__gif_px_to_bg(
+                                       &gif->global_colour_table[0],
+                                       gif->info.background);
+               }
+       }
+
+       if (gif->lzw_ctx == NULL) {
+               lzw_result res = lzw_context_create(
+                               (struct lzw_ctx **)&gif->lzw_ctx);
+               if (res != LZW_OK) {
+                       return nsgif__error_from_lzw(res);
+               }
+       }
+
+       /* Try to initialise all frames. */
+       do {
+               frames = gif->info.frame_count;
+               ret = nsgif__process_frame(gif, frames, false);
+       } while (gif->info.frame_count > frames);
+
+       if (ret == NSGIF_ERR_END_OF_DATA && gif->info.frame_count > 0) {
+               ret = NSGIF_OK;
+       }
+
+       return ret;
+}
+
+static void nsgif__redraw_rect_extend(
+               const nsgif_rect_t *frame,
+               nsgif_rect_t *redraw)
+{
+       if (redraw->x1 == 0 || redraw->y1 == 0) {
+               *redraw = *frame;
+       } else {
+               if (redraw->x0 > frame->x0) {
+                       redraw->x0 = frame->x0;
+               }
+               if (redraw->x1 < frame->x1) {
+                       redraw->x1 = frame->x1;
+               }
+               if (redraw->y0 > frame->y0) {
+                       redraw->y0 = frame->y0;
+               }
+               if (redraw->y1 < frame->y1) {
+                       redraw->y1 = frame->y1;
+               }
+       }
+}
+
+static uint32_t nsgif__frame_next(
+               nsgif_t *gif,
+               bool partial,
+               uint32_t frame)
+{
+       uint32_t frames = partial ?
+                       gif->frame_count_partial :
+                       gif->info.frame_count;
+
+       if (frames == 0) {
+               return NSGIF_FRAME_INVALID;
+       }
+
+       frame++;
+       return (frame >= frames) ? 0 : frame;
+}
+
+static nsgif_error nsgif__next_displayable_frame(
+               nsgif_t *gif,
+               uint32_t *frame,
+               uint32_t *delay)
+{
+       uint32_t next = *frame;
+
+       do {
+               next = nsgif__frame_next(gif, false, next);
+               if (next == *frame || next == NSGIF_FRAME_INVALID) {
+                       return NSGIF_ERR_FRAME_DISPLAY;
+               }
+
+               if (delay != NULL) {
+                       *delay += gif->frames[next].info.delay;
+               }
+
+       } while (gif->frames[next].info.display == false);
+
+       *frame = next;
+       return NSGIF_OK;
+}
+
+static inline bool nsgif__animation_complete(int count, int max)
+{
+       if (max == 0) {
+               return false;
+       }
+
+       return (count >= max);
+}
+
+nsgif_error nsgif_reset(
+               nsgif_t *gif)
+{
+       gif->info.loop_count = 0;
+       gif->frame = NSGIF_FRAME_INVALID;
+
+       return NSGIF_OK;
+}
+
+/* exported function documented in nsgif.h */
+nsgif_error nsgif_frame_prepare(
+               nsgif_t *gif,
+               nsgif_rect_t *area,
+               uint32_t *delay_cs,
+               uint32_t *frame_new)
+{
+       nsgif_error ret;
+       nsgif_rect_t rect = {
+               .x1 = 0,
+               .y1 = 0,
+       };
+       uint32_t delay = 0;
+       uint32_t frame = gif->frame;
+
+       if (gif->frame != NSGIF_FRAME_INVALID &&
+           gif->frame < gif->info.frame_count &&
+           gif->frames[gif->frame].info.display) {
+               rect = gif->frames[gif->frame].info.rect;
+       }
+
+       if (nsgif__animation_complete(
+                       gif->info.loop_count,
+                       gif->info.loop_max)) {
+               return NSGIF_ERR_ANIMATION_END;
+       }
+
+       ret = nsgif__next_displayable_frame(gif, &frame, &delay);
+       if (ret != NSGIF_OK) {
+               return ret;
+       }
+
+       if (gif->frame != NSGIF_FRAME_INVALID && frame < gif->frame) {
+               gif->info.loop_count++;
+       }
+
+       if (gif->info.frame_count == 1) {
+               delay = NSGIF_INFINITE;
+
+       } else if (gif->info.loop_max != 0) {
+               uint32_t frame_next = frame;
+               ret = nsgif__next_displayable_frame(gif, &frame_next, NULL);
+               if (ret != NSGIF_OK) {
+                       return ret;
+               }
+
+               if (frame_next < frame) {
+                       if (nsgif__animation_complete(
+                                       gif->info.loop_count + 1,
+                                       gif->info.loop_max)) {
+                               delay = NSGIF_INFINITE;
+                       }
+               }
+       }
+
+       gif->frame = frame;
+       nsgif__redraw_rect_extend(&gif->frames[frame].info.rect, &rect);
+
+       if (delay < gif->delay_min) {
+               delay = gif->delay_default;
+       }
+
+       *frame_new = gif->frame;
+       *delay_cs = delay;
+       *area = rect;
+
+       return NSGIF_OK;
+}
+
+/* exported function documented in nsgif.h */
+nsgif_error nsgif_frame_decode(
+               nsgif_t *gif,
+               uint32_t frame,
+               nsgif_bitmap_t **bitmap)
+{
+       uint32_t start_frame;
+       nsgif_error ret = NSGIF_OK;
+
+       if (frame >= gif->info.frame_count) {
+               return NSGIF_ERR_BAD_FRAME;
+       }
+
+       if (gif->decoded_frame == frame) {
+               *bitmap = gif->frame_image;
+               return NSGIF_OK;
+
+       } else if (gif->decoded_frame >= frame ||
+                  gif->decoded_frame == NSGIF_FRAME_INVALID) {
+               /* Can skip to first frame or restart. */
+               start_frame = 0;
+       } else {
+               start_frame = nsgif__frame_next(
+                               gif, false, gif->decoded_frame);
+       }
+
+       for (uint32_t f = start_frame; f <= frame; f++) {
+               ret = nsgif__process_frame(gif, f, true);
+               if (ret != NSGIF_OK) {
+                       return ret;
+               }
+       }
+
+       *bitmap = gif->frame_image;
+       return ret;
+}
+
+/* exported function documented in nsgif.h */
+const nsgif_info_t *nsgif_get_info(const nsgif_t *gif)
+{
+       return &gif->info;
+}
+
+/* exported function documented in nsgif.h */
+const nsgif_frame_info_t *nsgif_get_frame_info(
+               const nsgif_t *gif,
+               uint32_t frame)
+{
+       if (frame >= gif->info.frame_count) {
+               return NULL;
+       }
+
+       return &gif->frames[frame].info;
+}
+
+/* exported function documented in nsgif.h */
+const char *nsgif_strerror(nsgif_error err)
+{
+       static const char *const str[] = {
+               [NSGIF_OK]                = "Success",
+               [NSGIF_ERR_OOM]           = "Out of memory",
+               [NSGIF_ERR_DATA]          = "Invalid source data",
+               [NSGIF_ERR_BAD_FRAME]     = "Requested frame does not exist",
+               [NSGIF_ERR_DATA_FRAME]    = "Invalid frame data",
+               [NSGIF_ERR_FRAME_COUNT]   = "Excessive number of frames",
+               [NSGIF_ERR_END_OF_DATA]   = "Unexpected end of GIF source data",
+               [NSGIF_ERR_FRAME_DISPLAY] = "Frame can't be displayed",
+               [NSGIF_ERR_ANIMATION_END] = "Animation complete",
+       };
+
+       if (err >= NSGIF_ARRAY_LEN(str) || str[err] == NULL) {
+               return "Unknown error";
+       }
+
+       return str[err];
+}
+
+/* exported function documented in nsgif.h */
+const char *nsgif_str_disposal(enum nsgif_disposal disposal)
+{
+       static const char *const str[] = {
+               [NSGIF_DISPOSAL_UNSPECIFIED]   = "Unspecified",
+               [NSGIF_DISPOSAL_NONE]          = "None",
+               [NSGIF_DISPOSAL_RESTORE_BG]    = "Restore background",
+               [NSGIF_DISPOSAL_RESTORE_PREV]  = "Restore previous",
+               [NSGIF_DISPOSAL_RESTORE_QUIRK] = "Restore quirk",
+       };
+
+       if (disposal >= NSGIF_ARRAY_LEN(str) || str[disposal] == NULL) {
+               return "Unspecified";
+       }
+
+       return str[disposal];
+}
diff --git a/subprojects/libnsgif/lzw.c b/subprojects/libnsgif/lzw.c
index 31cf7d4e9..6f85caab1 100644
--- a/subprojects/libnsgif/lzw.c
+++ b/subprojects/libnsgif/lzw.c
@@ -4,6 +4,7 @@
  *                http://www.opensource.org/licenses/mit-license.php
  *
  * Copyright 2017 Michael Drake <michael drake codethink co uk>
+ * Copyright 2021 Michael Drake <tlsa netsurf-browser org>
  */
 
 #include <assert.h>
@@ -20,6 +21,8 @@
  * Decoder for GIF LZW data.
  */
 
+/** Maximum number of lzw table entries. */
+#define LZW_TABLE_ENTRY_MAX (1u << LZW_CODE_MAX)
 
 /**
  * Context for reading LZW data.
@@ -33,57 +36,65 @@
  * Note that an individual LZW code can be split over up to three sub-blocks.
  */
 struct lzw_read_ctx {
-       const uint8_t *data;    /**< Pointer to start of input data */
+       const uint8_t *restrict data;    /**< Pointer to start of input data */
        uint32_t data_len;      /**< Input data length */
        uint32_t data_sb_next;  /**< Offset to sub-block size */
 
        const uint8_t *sb_data; /**< Pointer to current sub-block in data */
-       uint32_t sb_bit;        /**< Current bit offset in sub-block */
+       size_t sb_bit;          /**< Current bit offset in sub-block */
        uint32_t sb_bit_count;  /**< Bit count in sub-block */
 };
 
 /**
- * LZW dictionary entry.
+ * LZW table entry.
  *
- * Records in the dictionary are composed of 1 or more entries.
- * Entries point to previous entries which can be followed to compose
+ * Records in the table are composed of 1 or more entries.
+ * Entries refer to the entry they extend which can be followed to compose
  * the complete record.  To compose the record in reverse order, take
- * the `last_value` from each entry, and move to the previous entry.
- * If the previous_entry's index is < the current clear_code, then it
+ * the `value` from each entry, and move to the entry it extends.
+ * If the extended entries index is < the current clear_code, then it
  * is the last entry in the record.
  */
-struct lzw_dictionary_entry {
-       uint8_t last_value;      /**< Last value for record ending at entry. */
-       uint8_t first_value;     /**< First value for entry's record. */
-       uint16_t previous_entry; /**< Offset in dictionary to previous entry. */
+struct lzw_table_entry {
+       uint8_t  value;   /**< Last value for record ending at entry. */
+       uint8_t  first;   /**< First value in entry's entire record. */
+       uint16_t count;   /**< Count of values in this entry's record. */
+       uint16_t extends; /**< Offset in table to previous entry. */
 };
 
 /**
  * LZW decompression context.
  */
 struct lzw_ctx {
-       /** Input reading context */
-       struct lzw_read_ctx input;
+       struct lzw_read_ctx input; /**< Input reading context */
 
-       uint32_t previous_code;       /**< Code read from input previously. */
-       uint32_t previous_code_first; /**< First value of previous code. */
+       uint16_t prev_code;       /**< Code read from input previously. */
+       uint16_t prev_code_first; /**< First value of previous code. */
+       uint16_t prev_code_count; /**< Total values for previous code. */
 
-       uint32_t initial_code_size;     /**< Starting LZW code size. */
-       uint32_t current_code_size;     /**< Current LZW code size. */
-       uint32_t current_code_size_max; /**< Max code value for current size. */
+       uint8_t  initial_code_size; /**< Starting LZW code size. */
 
-       uint32_t clear_code; /**< Special Clear code value */
-       uint32_t eoi_code;   /**< Special End of Information code value */
+       uint8_t  code_size; /**< Current LZW code size. */
+       uint16_t code_max;  /**< Max code value for current code size. */
 
-       uint32_t current_entry; /**< Next position in table to fill. */
+       uint16_t clear_code; /**< Special Clear code value */
+       uint16_t eoi_code;   /**< Special End of Information code value */
 
-       /** Output value stack. */
-       uint8_t stack_base[1 << LZW_CODE_MAX];
+       uint16_t table_size; /**< Next position in table to fill. */
 
-       /** LZW decode dictionary. Generated during decode. */
-       struct lzw_dictionary_entry table[1 << LZW_CODE_MAX];
-};
+       uint16_t output_code; /**< Code that has been partially output. */
+       uint16_t output_left; /**< Number of values left for output_code. */
+
+       bool     has_transparency;     /**< Whether the image is opaque. */
+       uint8_t  transparency_idx;     /**< Index representing transparency. */
+       const uint32_t *restrict colour_map; /**< Index to colour mapping. */
 
+       /** LZW code table. Generated during decode. */
+       struct lzw_table_entry table[LZW_TABLE_ENTRY_MAX];
+
+       /** Output value stack. */
+       uint8_t stack_base[LZW_TABLE_ENTRY_MAX];
+};
 
 /* Exported function, documented in lzw.h */
 lzw_result lzw_context_create(struct lzw_ctx **ctx)
@@ -97,21 +108,19 @@ lzw_result lzw_context_create(struct lzw_ctx **ctx)
        return LZW_OK;
 }
 
-
 /* Exported function, documented in lzw.h */
 void lzw_context_destroy(struct lzw_ctx *ctx)
 {
        free(ctx);
 }
 
-
 /**
  * Advance the context to the next sub-block in the input data.
  *
  * \param[in] ctx  LZW reading context, updated on success.
  * \return LZW_OK or LZW_OK_EOD on success, appropriate error otherwise.
  */
-static lzw_result lzw__block_advance(struct lzw_read_ctx *ctx)
+static lzw_result lzw__block_advance(struct lzw_read_ctx *restrict ctx)
 {
        uint32_t block_size;
        uint32_t next_block_pos = ctx->data_sb_next;
@@ -141,7 +150,6 @@ static lzw_result lzw__block_advance(struct lzw_read_ctx *ctx)
        return LZW_OK;
 }
 
-
 /**
  * Get the next LZW code of given size from the raw input data.
  *
@@ -152,31 +160,27 @@ static lzw_result lzw__block_advance(struct lzw_read_ctx *ctx)
  * \param[out] code_out   Returns an LZW code on success.
  * \return LZW_OK or LZW_OK_EOD on success, appropriate error otherwise.
  */
-static inline lzw_result lzw__next_code(
-               struct lzw_read_ctx *ctx,
-               uint8_t code_size,
-               uint32_t *code_out)
+static inline lzw_result lzw__read_code(
+               struct lzw_read_ctx *restrict ctx,
+               uint16_t code_size,
+               uint16_t *restrict code_out)
 {
        uint32_t code = 0;
-       uint8_t current_bit = ctx->sb_bit & 0x7;
-       uint8_t byte_advance = (current_bit + code_size) >> 3;
+       uint32_t current_bit = ctx->sb_bit & 0x7;
 
-       assert(byte_advance <= 2);
-
-       if (ctx->sb_bit + code_size <= ctx->sb_bit_count) {
-               /* Fast path: code fully inside this sub-block */
+       if (ctx->sb_bit + 24 <= ctx->sb_bit_count) {
+               /* Fast path: read three bytes from this sub-block */
                const uint8_t *data = ctx->sb_data + (ctx->sb_bit >> 3);
-               switch (byte_advance) {
-                       case 2: code |= data[2] << 16; /* Fall through */
-                       case 1: code |= data[1] <<  8; /* Fall through */
-                       case 0: code |= data[0] <<  0;
-               }
+               code |= *data++ <<  0;
+               code |= *data++ <<  8;
+               code |= *data   << 16;
                ctx->sb_bit += code_size;
        } else {
                /* Slow path: code spans sub-blocks */
+               uint8_t byte_advance = (current_bit + code_size) >> 3;
                uint8_t byte = 0;
-               uint8_t bits_remaining_0 = (code_size < (8 - current_bit)) ?
-                               code_size : (8 - current_bit);
+               uint8_t bits_remaining_0 = (code_size < (8u - current_bit)) ?
+                               code_size : (8u - current_bit);
                uint8_t bits_remaining_1 = code_size - bits_remaining_0;
                uint8_t bits_used[3] = {
                        [0] = bits_remaining_0,
@@ -184,6 +188,8 @@ static inline lzw_result lzw__next_code(
                        [2] = bits_remaining_1 - 8,
                };
 
+               assert(byte_advance <= 2);
+
                while (true) {
                        const uint8_t *data = ctx->sb_data;
                        lzw_result res;
@@ -213,165 +219,395 @@ static inline lzw_result lzw__next_code(
        return LZW_OK;
 }
 
-
 /**
- * Clear LZW code dictionary.
+ * Handle clear code.
  *
- * \param[in]  ctx            LZW reading context, updated.
- * \param[out] stack_pos_out  Returns current stack position.
+ * \param[in]   ctx       LZW reading context, updated.
+ * \param[out]  code_out  Returns next code after a clear code.
  * \return LZW_OK or error code.
  */
-static lzw_result lzw__clear_codes(
+static inline lzw_result lzw__handle_clear(
                struct lzw_ctx *ctx,
-               const uint8_t ** const stack_pos_out)
+               uint16_t *code_out)
 {
-       uint32_t code;
-       uint8_t *stack_pos;
+       uint16_t code;
 
-       /* Reset dictionary building context */
-       ctx->current_code_size = ctx->initial_code_size + 1;
-       ctx->current_code_size_max = (1 << ctx->current_code_size) - 1;;
-       ctx->current_entry = (1 << ctx->initial_code_size) + 2;
+       /* Reset table building context */
+       ctx->code_size = ctx->initial_code_size;
+       ctx->code_max = (1 << ctx->initial_code_size) - 1;
+       ctx->table_size = ctx->eoi_code + 1;
 
        /* There might be a sequence of clear codes, so process them all */
        do {
-               lzw_result res = lzw__next_code(&ctx->input,
-                               ctx->current_code_size, &code);
+               lzw_result res = lzw__read_code(&ctx->input,
+                               ctx->code_size, &code);
                if (res != LZW_OK) {
                        return res;
                }
        } while (code == ctx->clear_code);
 
-       /* The initial code must be from the initial dictionary. */
+       /* The initial code must be from the initial table. */
        if (code > ctx->clear_code) {
                return LZW_BAD_ICODE;
        }
 
-       /* Record this initial code as "previous" code, needed during decode. */
-       ctx->previous_code = code;
-       ctx->previous_code_first = code;
-
-       /* Reset the stack, and add first non-clear code added as first item. */
-       stack_pos = ctx->stack_base;
-       *stack_pos++ = code;
-
-       *stack_pos_out = stack_pos;
+       *code_out = code;
        return LZW_OK;
 }
 
-
 /* Exported function, documented in lzw.h */
 lzw_result lzw_decode_init(
                struct lzw_ctx *ctx,
-               const uint8_t *compressed_data,
-               uint32_t compressed_data_len,
-               uint32_t compressed_data_pos,
-               uint8_t code_size,
-               const uint8_t ** const stack_base_out,
-               const uint8_t ** const stack_pos_out)
+               uint8_t minimum_code_size,
+               const uint8_t *input_data,
+               uint32_t input_length,
+               uint32_t input_pos)
 {
-       struct lzw_dictionary_entry *table = ctx->table;
+       struct lzw_table_entry *table = ctx->table;
+       lzw_result res;
+       uint16_t code;
+
+       if (minimum_code_size >= LZW_CODE_MAX) {
+               return LZW_BAD_ICODE;
+       }
 
        /* Initialise the input reading context */
-       ctx->input.data = compressed_data;
-       ctx->input.data_len = compressed_data_len;
-       ctx->input.data_sb_next = compressed_data_pos;
+       ctx->input.data = input_data;
+       ctx->input.data_len = input_length;
+       ctx->input.data_sb_next = input_pos;
 
        ctx->input.sb_bit = 0;
        ctx->input.sb_bit_count = 0;
 
-       /* Initialise the dictionary building context */
-       ctx->initial_code_size = code_size;
+       /* Initialise the table building context */
+       ctx->initial_code_size = minimum_code_size + 1;
 
-       ctx->clear_code = (1 << code_size) + 0;
-       ctx->eoi_code   = (1 << code_size) + 1;
+       ctx->clear_code = (1 << minimum_code_size) + 0;
+       ctx->eoi_code   = (1 << minimum_code_size) + 1;
 
-       /* Initialise the standard dictionary entries */
-       for (uint32_t i = 0; i < ctx->clear_code; ++i) {
-               table[i].first_value = i;
-               table[i].last_value  = i;
+       ctx->output_left = 0;
+
+       /* Initialise the standard table entries */
+       for (uint16_t i = 0; i < ctx->clear_code; i++) {
+               table[i].first = i;
+               table[i].value = i;
+               table[i].count = 1;
        }
 
-       *stack_base_out = ctx->stack_base;
-       return lzw__clear_codes(ctx, stack_pos_out);
-}
+       res = lzw__handle_clear(ctx, &code);
+       if (res != LZW_OK) {
+               return res;
+       }
+
+       /* Store details of this code as "previous code" to the context. */
+       ctx->prev_code_first = ctx->table[code].first;
+       ctx->prev_code_count = ctx->table[code].count;
+       ctx->prev_code = code;
+
+       /* Add code to context for immediate output. */
+       ctx->output_code = code;
+       ctx->output_left = 1;
 
+       ctx->has_transparency = false;
+       ctx->transparency_idx = 0;
+       ctx->colour_map = NULL;
+
+       return LZW_OK;
+}
 
 /* Exported function, documented in lzw.h */
-lzw_result lzw_decode(struct lzw_ctx *ctx,
-               const uint8_t ** const stack_pos_out)
+lzw_result lzw_decode_init_map(
+               struct lzw_ctx *ctx,
+               uint8_t minimum_code_size,
+               uint32_t transparency_idx,
+               const uint32_t *colour_table,
+               const uint8_t *input_data,
+               uint32_t input_length,
+               uint32_t input_pos)
+{
+       lzw_result res;
+
+       if (colour_table == NULL) {
+               return LZW_BAD_PARAM;
+       }
+
+       res = lzw_decode_init(ctx, minimum_code_size,
+                       input_data, input_length, input_pos);
+       if (res != LZW_OK) {
+               return res;
+       }
+
+       ctx->has_transparency = (transparency_idx <= 0xFF);
+       ctx->transparency_idx = transparency_idx;
+       ctx->colour_map = colour_table;
+
+       return LZW_OK;
+}
+
+/**
+ * Create new table entry.
+ *
+ * \param[in]  ctx   LZW reading context, updated.
+ * \param[in]  code  Last value code for new table entry.
+ */
+static inline void lzw__table_add_entry(
+               struct lzw_ctx *ctx,
+               uint16_t code)
+{
+       struct lzw_table_entry *entry = &ctx->table[ctx->table_size];
+
+       entry->value = code;
+       entry->first = ctx->prev_code_first;
+       entry->count = ctx->prev_code_count + 1;
+       entry->extends = ctx->prev_code;
+
+       ctx->table_size++;
+}
+
+typedef uint32_t (*lzw_writer_fn)(
+               struct lzw_ctx *ctx,
+               void *restrict output_data,
+               uint32_t output_length,
+               uint32_t output_pos,
+               uint16_t code,
+               uint16_t left);
+
+/**
+ * Get the next LZW code and write its value(s) to output buffer.
+ *
+ * \param[in]     ctx             LZW reading context, updated.
+ * \param[in]     write_fn        Function for writing pixels to output.
+ * \param[in]     output_data     Array to write output values into.
+ * \param[in]     output_length   Size of output array.
+ * \param[in,out] output_written  Number of values written. Updated on exit.
+ * \return LZW_OK on success, or appropriate error code otherwise.
+ */
+static inline lzw_result lzw__decode(
+               struct lzw_ctx    *ctx,
+               lzw_writer_fn      write_fn,
+               void     *restrict output_data,
+               uint32_t           output_length,
+               uint32_t *restrict output_written)
 {
        lzw_result res;
-       uint32_t code_new;
-       uint32_t code_out;
-       uint8_t last_value;
-       uint8_t *stack_pos = ctx->stack_base;
-       uint32_t clear_code = ctx->clear_code;
-       uint32_t current_entry = ctx->current_entry;
-       struct lzw_dictionary_entry * const table = ctx->table;
+       uint16_t code;
 
        /* Get a new code from the input */
-       res = lzw__next_code(&ctx->input, ctx->current_code_size, &code_new);
+       res = lzw__read_code(&ctx->input, ctx->code_size, &code);
        if (res != LZW_OK) {
                return res;
        }
 
        /* Handle the new code */
-       if (code_new == clear_code) {
-               /* Got Clear code */
-               return lzw__clear_codes(ctx, stack_pos_out);
-
-       } else if (code_new == ctx->eoi_code) {
+       if (code == ctx->eoi_code) {
                /* Got End of Information code */
                return LZW_EOI_CODE;
 
-       } else if (code_new > current_entry) {
+       } else if (code > ctx->table_size) {
                /* Code is invalid */
                return LZW_BAD_CODE;
 
-       } else if (code_new < current_entry) {
-               /* Code is in table */
-               code_out = code_new;
-               last_value = table[code_new].first_value;
+       } else if (code == ctx->clear_code) {
+               res = lzw__handle_clear(ctx, &code);
+               if (res != LZW_OK) {
+                       return res;
+               }
+
+       } else if (ctx->table_size < LZW_TABLE_ENTRY_MAX) {
+               uint16_t size = ctx->table_size;
+               lzw__table_add_entry(ctx, (code < size) ?
+                               ctx->table[code].first :
+                               ctx->prev_code_first);
+
+               /* Ensure code size is increased, if needed. */
+               if (size == ctx->code_max && ctx->code_size < LZW_CODE_MAX) {
+                       ctx->code_size++;
+                       ctx->code_max = (1 << ctx->code_size) - 1;
+               }
+       }
+
+       *output_written += write_fn(ctx,
+                       output_data, output_length, *output_written,
+                       code, ctx->table[code].count);
+
+       /* Store details of this code as "previous code" to the context. */
+       ctx->prev_code_first = ctx->table[code].first;
+       ctx->prev_code_count = ctx->table[code].count;
+       ctx->prev_code = code;
+
+       return LZW_OK;
+}
+
+/**
+ * Write values for this code to the output stack.
+ *
+ * If there isn't enough space in the output stack, this function will write
+ * the as many as it can into the output.  If `ctx->output_left > 0` after
+ * this call, then there is more data for this code left to output.  The code
+ * is stored to the context as `ctx->output_code`.
+ *
+ * \param[in]  ctx           LZW reading context, updated.
+ * \param[in]  output_data   Array to write output values into.
+ * \param[in]  output_length length  Size of output array.
+ * \param[in]  output_used   Current position in output array.
+ * \param[in]  code          LZW code to output values for.
+ * \param[in]  left          Number of values remaining to output for this code.
+ * \return Number of pixel values written.
+ */
+static inline uint32_t lzw__write_fn(struct lzw_ctx *ctx,
+               void *restrict output_data,
+               uint32_t output_length,
+               uint32_t output_used,
+               uint16_t code,
+               uint16_t left)
+{
+       uint8_t *restrict output_pos = (uint8_t *)output_data + output_used;
+       const struct lzw_table_entry * const table = ctx->table;
+       uint32_t space = output_length - output_used;
+       uint16_t count = left;
+
+       if (count > space) {
+               left = count - space;
+               count = space;
        } else {
-               /* Code not in table */
-               *stack_pos++ = ctx->previous_code_first;
-               code_out = ctx->previous_code;
-               last_value = ctx->previous_code_first;
+               left = 0;
        }
 
-       /* Add to the dictionary, only if there's space */
-       if (current_entry < (1 << LZW_CODE_MAX)) {
-               struct lzw_dictionary_entry *entry = table + current_entry;
-               entry->last_value     = last_value;
-               entry->first_value    = ctx->previous_code_first;
-               entry->previous_entry = ctx->previous_code;
-               ctx->current_entry++;
+       ctx->output_code = code;
+       ctx->output_left = left;
+
+       /* Skip over any values we don't have space for. */
+       for (unsigned i = left; i != 0; i--) {
+               const struct lzw_table_entry *entry = table + code;
+               code = entry->extends;
+       }
+
+       output_pos += count;
+       for (unsigned i = count; i != 0; i--) {
+               const struct lzw_table_entry *entry = table + code;
+               *--output_pos = entry->value;
+               code = entry->extends;
+       }
+
+       return count;
+}
+
+/* Exported function, documented in lzw.h */
+lzw_result lzw_decode(struct lzw_ctx *ctx,
+               const uint8_t *restrict *const restrict output_data,
+               uint32_t *restrict                      output_written)
+{
+       const uint32_t output_length = sizeof(ctx->stack_base);
+
+       *output_written = 0;
+       *output_data = ctx->stack_base;
+
+       if (ctx->output_left != 0) {
+               *output_written += lzw__write_fn(ctx,
+                               ctx->stack_base, output_length, *output_written,
+                               ctx->output_code, ctx->output_left);
        }
 
-       /* Ensure code size is increased, if needed. */
-       if (current_entry == ctx->current_code_size_max) {
-               if (ctx->current_code_size < LZW_CODE_MAX) {
-                       ctx->current_code_size++;
-                       ctx->current_code_size_max =
-                                       (1 << ctx->current_code_size) - 1;
+       while (*output_written != output_length) {
+               lzw_result res = lzw__decode(ctx, lzw__write_fn,
+                               ctx->stack_base, output_length, output_written);
+               if (res != LZW_OK) {
+                       return res;
                }
        }
 
-       /* Store details of this code as "previous code" to the context. */
-       ctx->previous_code_first = table[code_new].first_value;
-       ctx->previous_code = code_new;
-
-       /* Put rest of data for this code on output stack.
-        * Note, in the case of "code not in table", the last entry of the
-        * current code has already been placed on the stack above. */
-       while (code_out > clear_code) {
-               struct lzw_dictionary_entry *entry = table + code_out;
-               *stack_pos++ = entry->last_value;
-               code_out = entry->previous_entry;
+       return LZW_OK;
+}
+
+/**
+ * Write colour mapped values for this code to the output.
+ *
+ * If there isn't enough space in the output stack, this function will write
+ * the as many as it can into the output.  If `ctx->output_left > 0` after
+ * this call, then there is more data for this code left to output.  The code
+ * is stored to the context as `ctx->output_code`.
+ *
+ * \param[in]  ctx            LZW reading context, updated.
+ * \param[in]  output_data    Array to write output values into.
+ * \param[in]  output_length  Size of output array.
+ * \param[in]  output_used    Current position in output array.
+ * \param[in]  code           LZW code to output values for.
+ * \param[in]  left           Number of values remaining to output for code.
+ * \return Number of pixel values written.
+ */
+static inline uint32_t lzw__map_write_fn(struct lzw_ctx *ctx,
+               void *restrict output_data,
+               uint32_t output_length,
+               uint32_t output_used,
+               uint16_t code,
+               uint16_t left)
+{
+       uint32_t *restrict output_pos = (uint32_t *)output_data + output_used;
+       const struct lzw_table_entry * const table = ctx->table;
+       uint32_t space = output_length - output_used;
+       uint16_t count = left;
+
+       if (count > space) {
+               left = count - space;
+               count = space;
+       } else {
+               left = 0;
+       }
+
+       ctx->output_code = code;
+       ctx->output_left = left;
+
+       for (unsigned i = left; i != 0; i--) {
+               const struct lzw_table_entry *entry = table + code;
+               code = entry->extends;
+       }
+
+       output_pos += count;
+       if (ctx->has_transparency) {
+               for (unsigned i = count; i != 0; i--) {
+                       const struct lzw_table_entry *entry = table + code;
+                       --output_pos;
+                       if (entry->value != ctx->transparency_idx) {
+                               *output_pos = ctx->colour_map[entry->value];
+                       }
+                       code = entry->extends;
+               }
+       } else {
+               for (unsigned i = count; i != 0; i--) {
+                       const struct lzw_table_entry *entry = table + code;
+                       *--output_pos = ctx->colour_map[entry->value];
+                       code = entry->extends;
+               }
+       }
+
+       return count;
+}
+
+/* Exported function, documented in lzw.h */
+lzw_result lzw_decode_map(struct lzw_ctx *ctx,
+               uint32_t *restrict output_data,
+               uint32_t           output_length,
+               uint32_t *restrict output_written)
+{
+       *output_written = 0;
+
+       if (ctx->colour_map == NULL) {
+               return LZW_NO_COLOUR;
+       }
+
+       if (ctx->output_left != 0) {
+               *output_written += lzw__map_write_fn(ctx,
+                               output_data, output_length, *output_written,
+                               ctx->output_code, ctx->output_left);
+       }
+
+       while (*output_written != output_length) {
+               lzw_result res = lzw__decode(ctx, lzw__map_write_fn,
+                               output_data, output_length, output_written);
+               if (res != LZW_OK) {
+                       return res;
+               }
        }
-       *stack_pos++ = table[code_out].last_value;
 
-       *stack_pos_out = stack_pos;
        return LZW_OK;
 }
diff --git a/subprojects/libnsgif/lzw.h b/subprojects/libnsgif/lzw.h
index 385b42557..c68753a4a 100644
--- a/subprojects/libnsgif/lzw.h
+++ b/subprojects/libnsgif/lzw.h
@@ -4,6 +4,7 @@
  *                http://www.opensource.org/licenses/mit-license.php
  *
  * Copyright 2017 Michael Drake <michael drake codethink co uk>
+ * Copyright 2021 Michael Drake <tlsa netsurf-browser org>
  */
 
 #ifndef LZW_H_
@@ -16,15 +17,12 @@
  * Decoder for GIF LZW data.
  */
 
-
 /** Maximum LZW code size in bits */
 #define LZW_CODE_MAX 12
 
-
 /* Declare lzw internal context structure */
 struct lzw_ctx;
 
-
 /** LZW decoding response codes */
 typedef enum lzw_result {
        LZW_OK,        /**< Success */
@@ -32,11 +30,12 @@ typedef enum lzw_result {
        LZW_NO_MEM,    /**< Error: Out of memory */
        LZW_NO_DATA,   /**< Error: Out of data */
        LZW_EOI_CODE,  /**< Error: End of Information code */
+       LZW_NO_COLOUR, /**< Error: No colour map provided. */
        LZW_BAD_ICODE, /**< Error: Bad initial LZW code */
+       LZW_BAD_PARAM, /**< Error: Bad function parameter. */
        LZW_BAD_CODE,  /**< Error: Bad LZW code */
 } lzw_result;
 
-
 /**
  * Create an LZW decompression context.
  *
@@ -58,48 +57,81 @@ void lzw_context_destroy(
 /**
  * Initialise an LZW decompression context for decoding.
  *
- * Caller owns neither `stack_base_out` or `stack_pos_out`.
- *
- * \param[in]  ctx                  The LZW decompression context to initialise.
- * \param[in]  compressed_data      The compressed data.
- * \param[in]  compressed_data_len  Byte length of compressed data.
- * \param[in]  compressed_data_pos  Start position in data.  Must be position
- *                                  of a size byte at sub-block start.
- * \param[in]  code_size            The initial LZW code size to use.
- * \param[out] stack_base_out       Returns base of decompressed data stack.
- * \param[out] stack_pos_out        Returns current stack position.
- *                                  There are `stack_pos_out - stack_base_out`
- *                                  current stack entries.
+ * \param[in]  ctx                The LZW decompression context to initialise.
+ * \param[in]  minimum_code_size  The LZW Minimum Code Size.
+ * \param[in]  input_data         The compressed data.
+ * \param[in]  input_length       Byte length of compressed data.
+ * \param[in]  input_pos          Start position in data.  Must be position
+ *                                of a size byte at sub-block start.
  * \return LZW_OK on success, or appropriate error code otherwise.
  */
 lzw_result lzw_decode_init(
                struct lzw_ctx *ctx,
-               const uint8_t *compressed_data,
-               uint32_t compressed_data_len,
-               uint32_t compressed_data_pos,
-               uint8_t code_size,
-               const uint8_t ** const stack_base_out,
-               const uint8_t ** const stack_pos_out);
+               uint8_t minimum_code_size,
+               const uint8_t *input_data,
+               uint32_t input_length,
+               uint32_t input_pos);
 
 /**
- * Fill the LZW stack with decompressed data
+ * Read input codes until end of LZW context owned output buffer.
+ *
+ * Ensure anything in output is used before calling this, as anything
+ * there before this call will be trampled.
  *
- * Ensure anything on the stack is used before calling this, as anything
- * on the stack before this call will be trampled.
+ * \param[in]  ctx             LZW reading context, updated.
+ * \param[out] output_data     Returns pointer to array of output values.
+ * \param[out] output_written  Returns the number of values written to data.
+ * \return LZW_OK on success, or appropriate error code otherwise.
+ */
+lzw_result lzw_decode(struct lzw_ctx *ctx,
+               const uint8_t *restrict *const restrict output_data,
+               uint32_t *restrict                      output_written);
+
+/**
+ * Initialise an LZW decompression context for decoding to colour map values.
  *
- * Caller does not own `stack_pos_out`.
+ * For transparency to work correctly, the given client buffer must have
+ * the values from the previous frame.  The transparency_idx should be a value
+ * of 256 or above, if the frame does not have transparency.
  *
- * \param[in]  ctx            LZW reading context, updated.
- * \param[out] stack_pos_out  Returns current stack position.
- *                            Use with `stack_base_out` value from previous
- *                            lzw_decode_init() call.
- *                            There are `stack_pos_out - stack_base_out`
- *                            current stack entries.
+ * \param[in]  ctx                The LZW decompression context to initialise.
+ * \param[in]  minimum_code_size  The LZW Minimum Code Size.
+ * \param[in]  transparency_idx   Index representing transparency.
+ * \param[in]  colour_table       Index to pixel colour mapping.
+ * \param[in]  input_data         The compressed data.
+ * \param[in]  input_length       Byte length of compressed data.
+ * \param[in]  input_pos          Start position in data.  Must be position
+ *                                of a size byte at sub-block start.
  * \return LZW_OK on success, or appropriate error code otherwise.
  */
-lzw_result lzw_decode(
+lzw_result lzw_decode_init_map(
                struct lzw_ctx *ctx,
-               const uint8_t ** const stack_pos_out);
+               uint8_t minimum_code_size,
+               uint32_t transparency_idx,
+               const uint32_t *colour_table,
+               const uint8_t *input_data,
+               uint32_t input_length,
+               uint32_t input_pos);
 
+/**
+ * Read LZW codes into client buffer, mapping output to colours.
+ *
+ * The context must have been initialised using \ref lzw_decode_init_map
+ * before calling this function, in order to provide the colour mapping table
+ * and any transparency index.
+ *
+ * Ensure anything in output is used before calling this, as anything
+ * there before this call will be trampled.
+ *
+ * \param[in]  ctx             LZW reading context, updated.
+ * \param[in]  output_data     Client buffer to fill with colour mapped values.
+ * \param[in]  output_length   Size of output array.
+ * \param[out] output_written  Returns the number of values written to data.
+ * \return LZW_OK on success, or appropriate error code otherwise.
+ */
+lzw_result lzw_decode_map(struct lzw_ctx *ctx,
+               uint32_t *restrict output_data,
+               uint32_t           output_length,
+               uint32_t *restrict output_written);
 
 #endif
diff --git a/subprojects/libnsgif/meson.build b/subprojects/libnsgif/meson.build
index 273d4ca26..ee658554e 100644
--- a/subprojects/libnsgif/meson.build
+++ b/subprojects/libnsgif/meson.build
@@ -5,7 +5,7 @@ project('libnsgif',
 
 
 libnsgif_lib = static_library('nsgif',
-  'libnsgif.c',
+  'gif.c',
   'lzw.c',
 )
 
diff --git a/subprojects/libnsgif/nsgif.h b/subprojects/libnsgif/nsgif.h
new file mode 100644
index 000000000..54dcd7190
--- /dev/null
+++ b/subprojects/libnsgif/nsgif.h
@@ -0,0 +1,356 @@
+/*
+ * Copyright 2004 Richard Wilson <richard wilson netsurf-browser org>
+ * Copyright 2008 Sean Fox <dyntryx gmail com>
+ * Copyright 2013-2022 Michael Drake <tlsa netsurf-browser org>
+ *
+ * This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
+ * Licenced under the MIT License,
+ *                http://www.opensource.org/licenses/mit-license.php
+ */
+
+/**
+ * \file
+ * Interface to progressive animated GIF file decoding.
+ */
+
+#ifndef NSNSGIF_H
+#define NSNSGIF_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <inttypes.h>
+
+/** Representation of infinity. */
+#define NSGIF_INFINITE (UINT32_MAX)
+
+/**
+ * Opaque type used by LibNSGIF to represent a GIF object in memory.
+ */
+typedef struct nsgif nsgif_t;
+
+/**
+ * LibNSGIF rectangle structure.
+ *
+ * * Top left coordinate is `(x0, y0)`.
+ * * Width is `x1 - x0`.
+ * * Height is `y1 - y0`.
+ * * Units are pixels.
+ */
+typedef struct nsgif_rect {
+       /** x co-ordinate of redraw rectangle, left */
+       uint32_t x0;
+       /** y co-ordinate of redraw rectangle, top */
+       uint32_t y0;
+       /** x co-ordinate of redraw rectangle, right */
+       uint32_t x1;
+       /** y co-ordinate of redraw rectangle, bottom */
+       uint32_t y1;
+} nsgif_rect_t;
+
+/**
+ * LibNSGIF return codes.
+ */
+typedef enum {
+       /**
+        * Success.
+        */
+       NSGIF_OK,
+
+       /**
+        * Out of memory error.
+        */
+       NSGIF_ERR_OOM,
+
+       /**
+        * GIF source data is invalid, and no frames are recoverable.
+        */
+       NSGIF_ERR_DATA,
+
+       /**
+        * Frame number is not valid.
+        */
+       NSGIF_ERR_BAD_FRAME,
+
+       /**
+        * GIF source data contained an error in a frame.
+        */
+       NSGIF_ERR_DATA_FRAME,
+
+       /**
+        * Too many frames.
+        */
+       NSGIF_ERR_FRAME_COUNT,
+
+       /**
+        * Unexpected end of GIF source data.
+        */
+       NSGIF_ERR_END_OF_DATA,
+
+       /**
+        * The current frame cannot be displayed.
+        */
+       NSGIF_ERR_FRAME_DISPLAY,
+
+       /**
+        * Indicates an animation is complete, and \ref nsgif_reset must be
+        * called to restart the animation from the beginning.
+        */
+       NSGIF_ERR_ANIMATION_END,
+} nsgif_error;
+
+/**
+ * Client bitmap type.
+ *
+ * These are client-created and destroyed, via the \ref bitmap callbacks,
+ * but they are owned by a \ref nsgif_t.
+ *
+ * The pixel buffer is is 32bpp, treated as individual bytes in the component
+ * order RR GG BB AA. For example, a 1x1 image with a single orange pixel would
+ * be encoded as the following sequence of bytes: 0xff, 0x88, 0x00, 0x00.
+ */
+typedef void nsgif_bitmap_t;
+
+/** Bitmap callbacks function table */
+typedef struct nsgif_bitmap_cb_vt {
+       /**
+        * Callback to create a bitmap with the given dimensions.
+        *
+        * \param[in]  width   Required bitmap width in pixels.
+        * \param[in]  height  Required bitmap height in pixels.
+        * \return pointer to client's bitmap structure or NULL on error.
+        */
+       nsgif_bitmap_t* (*create)(int width, int height);
+
+       /**
+        * Callback to free a bitmap.
+        *
+        * \param[in]  bitmap  The bitmap to destroy.
+        */
+       void (*destroy)(nsgif_bitmap_t *bitmap);
+
+       /**
+        * Get pointer to pixel buffer in a bitmap.
+        *
+        * The pixel buffer must be `width * height * sizeof(uint32_t)`.
+        * Note that the returned pointer to uint8_t must be 4-byte aligned.
+        *
+        * \param[in]  bitmap  The bitmap.
+        * \return pointer to bitmap's pixel buffer.
+        */
+       uint8_t* (*get_buffer)(nsgif_bitmap_t *bitmap);
+
+       /* The following functions are optional. */
+
+       /**
+        * Set whether a bitmap can be plotted opaque.
+        *
+        * \param[in]  bitmap  The bitmap.
+        * \param[in]  opaque  Whether the current frame is opaque.
+        */
+       void (*set_opaque)(nsgif_bitmap_t *bitmap, bool opaque);
+
+       /**
+        * Tests whether a bitmap has an opaque alpha channel.
+        *
+        * \param[in]  bitmap  The bitmap.
+        * \return true if the bitmap is opaque, false otherwise.
+        */
+       bool (*test_opaque)(nsgif_bitmap_t *bitmap);
+
+       /**
+        * Bitmap modified notification.
+        *
+        * \param[in]  bitmap  The bitmap.
+        */
+       void (*modified)(nsgif_bitmap_t *bitmap);
+} nsgif_bitmap_cb_vt;
+
+/**
+ * Convert an error code to a string.
+ *
+ * \param[in]  err  The error code to convert.
+ * \return String representation of given error code.
+ */
+const char *nsgif_strerror(nsgif_error err);
+
+/**
+ * Create the NSGIF object.
+ *
+ * \param[in]  bitmap_vt  Bitmap operation functions v-table.
+ * \param[out] gif_out    Return \ref nsgif_t object on success.
+ *
+ * \return NSGIF_OK on success, or appropriate error otherwise.
+ */
+nsgif_error nsgif_create(
+               const nsgif_bitmap_cb_vt *bitmap_vt,
+               nsgif_t **gif_out);
+
+/**
+ * Free a NSGIF object.
+ *
+ * \param[in]  gif  The NSGIF to free.
+ */
+void nsgif_destroy(nsgif_t *gif);
+
+/**
+ * Scan the source image data.
+ *
+ * This is used to feed the source data into LibNSGIF. This must be called
+ * before calling \ref nsgif_frame_decode.
+ *
+ * It can be called multiple times with, with increasing sizes. If it is called
+ * several times, as more data is available (e.g. slow network fetch) the data
+ * already given to \ref nsgif_data_scan must be provided each time.
+ *
+ * For example, if you call \ref nsgif_data_scan with 25 bytes of data, and then
+ * fetch another 10 bytes, you would need to call \ref nsgif_data_scan with a
+ * size of 35 bytes, and the whole 35 bytes must be contiguous memory. It is
+ * safe to `realloc` the source buffer between calls to \ref nsgif_data_scan.
+ * (The actual data pointer is allowed to be different.)
+ *
+ * If an error occurs, all previously scanned frames are retained.
+ *
+ * Note that an error returned from this function is purely informational.
+ * So long as at least one frame is available, you can display frames.
+ *
+ * \param[in]  gif     The \ref nsgif_t object.
+ * \param[in]  size    Number of bytes in data.
+ * \param[in]  data    Raw source GIF data.
+ *
+ * \return NSGIF_OK on success, or appropriate error otherwise.
+ */
+nsgif_error nsgif_data_scan(
+               nsgif_t *gif,
+               size_t size,
+               const uint8_t *data);
+
+/**
+ * Prepare to show a frame.
+ *
+ * If this is the last frame of an animation with a finite loop count, the
+ * returned `delay_cs` will be \ref NSGIF_INFINITE, indicating that the frame
+ * should be shown forever.
+ *
+ * \param[in]  gif        The \ref nsgif_t object.
+ * \param[out] area       The area in pixels that must be redrawn.
+ * \param[out] delay_cs   Time to wait after frame_new before next frame in cs.
+ * \param[out] frame_new  The frame to decode.
+ *
+ * \return NSGIF_OK on success, or appropriate error otherwise.
+ */
+nsgif_error nsgif_frame_prepare(
+               nsgif_t *gif,
+               nsgif_rect_t *area,
+               uint32_t *delay_cs,
+               uint32_t *frame_new);
+
+/**
+ * Decodes a GIF frame.
+ *
+ * \param[in]  gif     The \ref nsgif_t object.
+ * \param[in]  frame   The frame number to decode.
+ * \param[out] bitmap  On success, returns pointer to the client-allocated,
+ *                     nsgif-owned client bitmap structure.
+ *
+ * \return NSGIF_OK on success, or appropriate error otherwise.
+ */
+nsgif_error nsgif_frame_decode(
+               nsgif_t *gif,
+               uint32_t frame,
+               nsgif_bitmap_t **bitmap);
+
+/**
+ * Reset a GIF animation.
+ *
+ * Some animations are only meant to loop N times, and then show the
+ * final frame forever. This function resets the loop and frame counters,
+ * so that the animation can be replayed without the overhead of recreating
+ * the \ref nsgif_t object and rescanning the raw data.
+ *
+ * \param[in]  gif  A \ref nsgif_t object.
+ *
+ * \return NSGIF_OK on success, or appropriate error otherwise.
+ */
+nsgif_error nsgif_reset(
+               nsgif_t *gif);
+
+/**
+ * Information about a GIF.
+ */
+typedef struct nsgif_info {
+       /** width of GIF (may increase during decoding) */
+       uint32_t width;
+       /** height of GIF (may increase during decoding) */
+       uint32_t height;
+       /** number of frames decoded */
+       uint32_t frame_count;
+       /** number of times to play animation (zero means loop forever) */
+       int loop_max;
+       /** number of animation loops so far */
+       int loop_count;
+       /** background colour in same pixel format as \ref nsgif_bitmap_t. */
+       uint8_t background[4];
+} nsgif_info_t;
+
+/**
+ * Frame disposal method.
+ *
+ * Clients do not need to know about this, it is provided purely for dumping
+ * raw information about GIF frames.
+ */
+enum nsgif_disposal {
+       NSGIF_DISPOSAL_UNSPECIFIED,   /**< No disposal method specified. */
+       NSGIF_DISPOSAL_NONE,          /**< Frame remains. */
+       NSGIF_DISPOSAL_RESTORE_BG,    /**< Clear frame to background colour. */
+       NSGIF_DISPOSAL_RESTORE_PREV,  /**< Restore previous frame. */
+       NSGIF_DISPOSAL_RESTORE_QUIRK, /**< Alias for NSGIF_DISPOSAL_RESTORE_PREV. */
+};
+
+/**
+ * Convert a disposal method to a string.
+ *
+ * \param[in]  disposal  The disposal method to convert.
+ * \return String representation of given disposal method.
+ */
+const char *nsgif_str_disposal(enum nsgif_disposal disposal);
+
+/**
+ * Information about a GIF frame.
+ */
+typedef struct nsgif_frame_info {
+       /** whether the frame should be displayed/animated */
+       bool display;
+       /** whether the frame may have transparency */
+       bool transparency;
+
+       /** Disposal method for previous frame; affects plotting */
+       uint8_t disposal;
+       /** delay (in cs) before animating the frame */
+       uint32_t delay;
+
+       /** Frame's redraw rectangle. */
+       nsgif_rect_t rect;
+} nsgif_frame_info_t;
+
+/**
+ * Get information about a GIF from an \ref nsgif_t object.
+ *
+ * \param[in]  gif  The \ref nsgif_t object to get info for.
+ *
+ * \return The gif info, or NULL on error.
+ */
+const nsgif_info_t *nsgif_get_info(const nsgif_t *gif);
+
+/**
+ * Get information about a GIF from an \ref nsgif_t object.
+ *
+ * \param[in]  gif    The \ref nsgif_t object to get frame info for.
+ * \param[in]  frame  The frame number to get info for.
+ *
+ * \return The gif frame info, or NULL on error.
+ */
+const nsgif_frame_info_t *nsgif_get_frame_info(
+               const nsgif_t *gif,
+               uint32_t frame);
+
+#endif


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]