From de0f5828f63d0db82c4157701a763796420eaa80 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Wed, 1 Dec 2021 14:02:42 +0000 Subject: LIB: Rename public header and source file. This allows the library header to have a name consistent with the library soname. --- include/libnsgif.h | 194 -------- include/nsgif.h | 194 ++++++++ src/gif.c | 1380 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/libnsgif.c | 1380 ---------------------------------------------------- 4 files changed, 1574 insertions(+), 1574 deletions(-) delete mode 100644 include/libnsgif.h create mode 100644 include/nsgif.h create mode 100644 src/gif.c delete mode 100644 src/libnsgif.c diff --git a/include/libnsgif.h b/include/libnsgif.h deleted file mode 100644 index 114addf..0000000 --- a/include/libnsgif.h +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2004 Richard Wilson - * Copyright 2008 Sean Fox - * Copyright 2013-2021 Michael Drake - * - * 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 -#include - -/* Error return values */ -typedef enum { - NSGIF_WORKING = 1, - NSGIF_OK = 0, - NSGIF_INSUFFICIENT_DATA = -1, - NSGIF_INSUFFICIENT_FRAME_DATA = NSGIF_INSUFFICIENT_DATA, - NSGIF_FRAME_DATA_ERROR = -2, - NSGIF_DATA_ERROR = -4, - NSGIF_INSUFFICIENT_MEMORY = -5, - NSGIF_FRAME_NO_DISPLAY = -6, - NSGIF_END_OF_FRAME = -7 -} nsgif_result; - -/** GIF frame data */ -typedef struct nsgif_frame { - /** whether the frame should be displayed/animated */ - bool display; - /** delay (in cs) before animating the frame */ - uint32_t frame_delay; - - /* Internal members are listed below */ - - /** 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; - /** how the previous frame should be disposed; affects plotting */ - uint8_t disposal_method; - /** whether we acknowledge transparency */ - bool transparency; - /** the index designating a transparent pixel */ - uint32_t transparency_index; - /** x co-ordinate of redraw rectangle */ - uint32_t redraw_x; - /** y co-ordinate of redraw rectangle */ - uint32_t redraw_y; - /** width of redraw rectangle */ - uint32_t redraw_width; - /** height of redraw rectangle */ - uint32_t redraw_height; - /* Frame flags */ - uint32_t flags; -} nsgif_frame; - -/* API for Bitmap callbacks */ -typedef void* (*nsgif_bitmap_cb_create)(int width, int height); -typedef void (*nsgif_bitmap_cb_destroy)(void *bitmap); -typedef uint8_t* (*nsgif_bitmap_cb_get_buffer)(void *bitmap); -typedef void (*nsgif_bitmap_cb_set_opaque)(void *bitmap, bool opaque); -typedef bool (*nsgif_bitmap_cb_test_opaque)(void *bitmap); -typedef void (*nsgif_bitmap_cb_modified)(void *bitmap); - -/** Bitmap callbacks function table */ -typedef struct nsgif_bitmap_cb_vt { - /** Create a bitmap. */ - nsgif_bitmap_cb_create create; - /** Free a bitmap. */ - nsgif_bitmap_cb_destroy destroy; - /** Return a pointer to the pixel data in a bitmap. */ - nsgif_bitmap_cb_get_buffer get_buffer; - - /* Members below are optional */ - - /** Sets whether a bitmap should be plotted opaque. */ - nsgif_bitmap_cb_set_opaque set_opaque; - /** Tests whether a bitmap has an opaque alpha channel. */ - nsgif_bitmap_cb_test_opaque test_opaque; - /** The bitmap image has changed, so flush any persistent cache. */ - nsgif_bitmap_cb_modified modified; -} nsgif_bitmap_cb_vt; - -/** GIF animation data */ -typedef struct nsgif_animation { - /** LZW decode context */ - void *lzw_ctx; - /** callbacks for bitmap functions */ - nsgif_bitmap_cb_vt bitmap; - /** pointer to GIF data */ - const uint8_t *nsgif_data; - /** 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 frames partially decoded */ - uint32_t frame_count_partial; - /** decoded frames */ - nsgif_frame *frames; - /** current frame decoded to bitmap */ - int decoded_frame; - /** currently decoded image; stored as bitmap from bitmap_create callback */ - void *frame_image; - /** number of times to loop animation */ - int loop_count; - - /* Internal members are listed below */ - - /** current index into GIF data */ - uint32_t buffer_position; - /** total number of bytes of GIF data available */ - uint32_t buffer_size; - /** current number of frame holders */ - uint32_t frame_holders; - /** background index */ - uint32_t bg_index; - /** background colour */ - uint32_t bg_colour; - /** 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; - /** global colour table */ - uint32_t *global_colour_table; - /** local colour table */ - uint32_t *local_colour_table; - /** current colour table */ - uint32_t *colour_table; - - /** previous frame for NSGIF_FRAME_RESTORE */ - void *prev_frame; - /** previous frame index */ - int prev_index; - /** previous frame width */ - unsigned prev_width; - /** previous frame height */ - unsigned prev_height; -} nsgif_animation; - -/** - * Initialises necessary nsgif_animation members. - */ -void nsgif_create(nsgif_animation *gif, nsgif_bitmap_cb_vt *bitmap_callbacks); - -/** - * Initialises any workspace held by the animation and attempts to decode - * any information that hasn't already been decoded. - * If an error occurs, all previously decoded frames are retained. - * - * \return Error return value. - * - NSGIF_FRAME_DATA_ERROR for GIF frame data error - * - NSGIF_INSUFFICIENT_DATA reached unexpected end of source data - * - NSGIF_INSUFFICIENT_MEMORY for memory error - * - NSGIF_DATA_ERROR for GIF error - * - NSGIF_OK for successful decoding - * - NSGIF_WORKING for successful decoding if more frames are expected - */ -nsgif_result nsgif_initialise(nsgif_animation *gif, size_t size, const uint8_t *data); - -/** - * Decodes a GIF frame. - * - * \return Error return value. - * - NSGIF_FRAME_DATA_ERROR for GIF frame data error - * - NSGIF_DATA_ERROR for GIF error (invalid frame header) - * - NSGIF_INSUFFICIENT_DATA reached unexpected end of source data - * - NSGIF_INSUFFICIENT_MEMORY for insufficient memory to process - * - NSGIF_OK for successful decoding - */ -nsgif_result nsgif_decode_frame(nsgif_animation *gif, uint32_t frame); - -/** - * Releases any workspace held by a gif - */ -void nsgif_finalise(nsgif_animation *gif); - -#endif diff --git a/include/nsgif.h b/include/nsgif.h new file mode 100644 index 0000000..114addf --- /dev/null +++ b/include/nsgif.h @@ -0,0 +1,194 @@ +/* + * Copyright 2004 Richard Wilson + * Copyright 2008 Sean Fox + * Copyright 2013-2021 Michael Drake + * + * 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 +#include + +/* Error return values */ +typedef enum { + NSGIF_WORKING = 1, + NSGIF_OK = 0, + NSGIF_INSUFFICIENT_DATA = -1, + NSGIF_INSUFFICIENT_FRAME_DATA = NSGIF_INSUFFICIENT_DATA, + NSGIF_FRAME_DATA_ERROR = -2, + NSGIF_DATA_ERROR = -4, + NSGIF_INSUFFICIENT_MEMORY = -5, + NSGIF_FRAME_NO_DISPLAY = -6, + NSGIF_END_OF_FRAME = -7 +} nsgif_result; + +/** GIF frame data */ +typedef struct nsgif_frame { + /** whether the frame should be displayed/animated */ + bool display; + /** delay (in cs) before animating the frame */ + uint32_t frame_delay; + + /* Internal members are listed below */ + + /** 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; + /** how the previous frame should be disposed; affects plotting */ + uint8_t disposal_method; + /** whether we acknowledge transparency */ + bool transparency; + /** the index designating a transparent pixel */ + uint32_t transparency_index; + /** x co-ordinate of redraw rectangle */ + uint32_t redraw_x; + /** y co-ordinate of redraw rectangle */ + uint32_t redraw_y; + /** width of redraw rectangle */ + uint32_t redraw_width; + /** height of redraw rectangle */ + uint32_t redraw_height; + /* Frame flags */ + uint32_t flags; +} nsgif_frame; + +/* API for Bitmap callbacks */ +typedef void* (*nsgif_bitmap_cb_create)(int width, int height); +typedef void (*nsgif_bitmap_cb_destroy)(void *bitmap); +typedef uint8_t* (*nsgif_bitmap_cb_get_buffer)(void *bitmap); +typedef void (*nsgif_bitmap_cb_set_opaque)(void *bitmap, bool opaque); +typedef bool (*nsgif_bitmap_cb_test_opaque)(void *bitmap); +typedef void (*nsgif_bitmap_cb_modified)(void *bitmap); + +/** Bitmap callbacks function table */ +typedef struct nsgif_bitmap_cb_vt { + /** Create a bitmap. */ + nsgif_bitmap_cb_create create; + /** Free a bitmap. */ + nsgif_bitmap_cb_destroy destroy; + /** Return a pointer to the pixel data in a bitmap. */ + nsgif_bitmap_cb_get_buffer get_buffer; + + /* Members below are optional */ + + /** Sets whether a bitmap should be plotted opaque. */ + nsgif_bitmap_cb_set_opaque set_opaque; + /** Tests whether a bitmap has an opaque alpha channel. */ + nsgif_bitmap_cb_test_opaque test_opaque; + /** The bitmap image has changed, so flush any persistent cache. */ + nsgif_bitmap_cb_modified modified; +} nsgif_bitmap_cb_vt; + +/** GIF animation data */ +typedef struct nsgif_animation { + /** LZW decode context */ + void *lzw_ctx; + /** callbacks for bitmap functions */ + nsgif_bitmap_cb_vt bitmap; + /** pointer to GIF data */ + const uint8_t *nsgif_data; + /** 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 frames partially decoded */ + uint32_t frame_count_partial; + /** decoded frames */ + nsgif_frame *frames; + /** current frame decoded to bitmap */ + int decoded_frame; + /** currently decoded image; stored as bitmap from bitmap_create callback */ + void *frame_image; + /** number of times to loop animation */ + int loop_count; + + /* Internal members are listed below */ + + /** current index into GIF data */ + uint32_t buffer_position; + /** total number of bytes of GIF data available */ + uint32_t buffer_size; + /** current number of frame holders */ + uint32_t frame_holders; + /** background index */ + uint32_t bg_index; + /** background colour */ + uint32_t bg_colour; + /** 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; + /** global colour table */ + uint32_t *global_colour_table; + /** local colour table */ + uint32_t *local_colour_table; + /** current colour table */ + uint32_t *colour_table; + + /** previous frame for NSGIF_FRAME_RESTORE */ + void *prev_frame; + /** previous frame index */ + int prev_index; + /** previous frame width */ + unsigned prev_width; + /** previous frame height */ + unsigned prev_height; +} nsgif_animation; + +/** + * Initialises necessary nsgif_animation members. + */ +void nsgif_create(nsgif_animation *gif, nsgif_bitmap_cb_vt *bitmap_callbacks); + +/** + * Initialises any workspace held by the animation and attempts to decode + * any information that hasn't already been decoded. + * If an error occurs, all previously decoded frames are retained. + * + * \return Error return value. + * - NSGIF_FRAME_DATA_ERROR for GIF frame data error + * - NSGIF_INSUFFICIENT_DATA reached unexpected end of source data + * - NSGIF_INSUFFICIENT_MEMORY for memory error + * - NSGIF_DATA_ERROR for GIF error + * - NSGIF_OK for successful decoding + * - NSGIF_WORKING for successful decoding if more frames are expected + */ +nsgif_result nsgif_initialise(nsgif_animation *gif, size_t size, const uint8_t *data); + +/** + * Decodes a GIF frame. + * + * \return Error return value. + * - NSGIF_FRAME_DATA_ERROR for GIF frame data error + * - NSGIF_DATA_ERROR for GIF error (invalid frame header) + * - NSGIF_INSUFFICIENT_DATA reached unexpected end of source data + * - NSGIF_INSUFFICIENT_MEMORY for insufficient memory to process + * - NSGIF_OK for successful decoding + */ +nsgif_result nsgif_decode_frame(nsgif_animation *gif, uint32_t frame); + +/** + * Releases any workspace held by a gif + */ +void nsgif_finalise(nsgif_animation *gif); + +#endif diff --git a/src/gif.c b/src/gif.c new file mode 100644 index 0000000..9d77894 --- /dev/null +++ b/src/gif.c @@ -0,0 +1,1380 @@ +/* + * Copyright 2004 Richard Wilson + * Copyright 2008 Sean Fox + * Copyright 2013-2021 Michael Drake + * + * 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 +#include +#include +#include +#include + +#include "lzw.h" +#include "nsgif.h" + +/** + * + * \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. + */ + +/** Maximum colour table size */ +#define NSGIF_MAX_COLOURS 256 + +/** 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_INVALID_FRAME -1 + +/** Transparent colour */ +#define NSGIF_TRANSPARENT_COLOUR 0x00 + +/** No transparency */ +#define NSGIF_NO_TRANSPARENCY (0xFFFFFFFFu) + +enum nsgif_disposal { + NSGIF_DISPOSAL_UNSPECIFIED, + NSGIF_DISPOSAL_NONE, + NSGIF_DISPOSAL_RESTORE_BG, + NSGIF_DISPOSAL_RESTORE_PREV, + NSGIF_DISPOSAL_RESTORE_QUIRK, /**< Alias for NSGIF_DISPOSAL_RESTORE_PREV. */ +}; + +/* GIF Flags */ +#define NSGIF_INTERLACE_MASK 0x40 +#define NSGIF_COLOUR_TABLE_MASK 0x80 +#define NSGIF_COLOUR_TABLE_SIZE_MASK 0x07 +#define NSGIF_EXTENSION_INTRODUCER 0x21 +#define NSGIF_EXTENSION_GRAPHIC_CONTROL 0xf9 +#define NSGIF_DISPOSAL_MASK 0x1c +#define NSGIF_TRANSPARENCY_MASK 0x01 +#define NSGIF_EXTENSION_COMMENT 0xfe +#define NSGIF_EXTENSION_PLAIN_TEXT 0x01 +#define NSGIF_EXTENSION_APPLICATION 0xff +#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_result nsgif__error_from_lzw(lzw_result l_res) +{ + static const nsgif_result g_res[] = { + [LZW_OK] = NSGIF_OK, + [LZW_OK_EOD] = NSGIF_END_OF_FRAME, + [LZW_NO_MEM] = NSGIF_INSUFFICIENT_MEMORY, + [LZW_NO_DATA] = NSGIF_INSUFFICIENT_DATA, + [LZW_EOI_CODE] = NSGIF_FRAME_DATA_ERROR, + [LZW_BAD_ICODE] = NSGIF_FRAME_DATA_ERROR, + [LZW_BAD_CODE] = NSGIF_FRAME_DATA_ERROR, + }; + 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_INSUFFICIENT_MEMORY for a memory error NSGIF_OK for success + */ +static nsgif_result nsgif__initialise_sprite( + struct nsgif_animation *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_INSUFFICIENT_MEMORY; + } + + 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_animation *gif) +{ + nsgif_result ret; + + /* Make sure we have a buffer to decode to. */ + ret = nsgif__initialise_sprite(gif, gif->width, gif->height); + if (ret != NSGIF_OK) { + return NULL; + } + + /* Get the frame data */ + assert(gif->bitmap.get_buffer); + return (uint32_t *)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_animation *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_animation *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_animation *gif) +{ + if (gif->bitmap.test_opaque) { + return gif->bitmap.test_opaque( + gif->frame_image); + } + + return false; +} + +static void nsgif__record_frame( + struct nsgif_animation *gif, + const uint32_t *bitmap) +{ + bool need_alloc = gif->prev_frame == NULL; + uint32_t *prev_frame; + + if (gif->decoded_frame == NSGIF_INVALID_FRAME || + 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 && + gif->width * gif->height > gif->prev_width * gif->prev_height) { + need_alloc = true; + } + + if (need_alloc) { + prev_frame = realloc(gif->prev_frame, + gif->width * gif->height * 4); + if (prev_frame == NULL) { + return; + } + } else { + prev_frame = gif->prev_frame; + } + + memcpy(prev_frame, bitmap, gif->width * gif->height * 4); + + gif->prev_frame = prev_frame; + gif->prev_width = gif->width; + gif->prev_height = gif->height; + gif->prev_index = gif->decoded_frame; +} + +static nsgif_result nsgif__recover_frame( + const struct nsgif_animation *gif, + uint32_t *bitmap) +{ + const uint32_t *prev_frame = gif->prev_frame; + unsigned height = gif->height < gif->prev_height ? gif->height : gif->prev_height; + unsigned width = gif->width < gif->prev_width ? gif->width : gif->prev_width; + + if (prev_frame == NULL) { + return NSGIF_FRAME_DATA_ERROR; + } + + for (unsigned y = 0; y < height; y++) { + memcpy(bitmap, prev_frame, width * 4); + + bitmap += gif->width; + prev_frame += gif->prev_width; + } + + 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); + } +} + +static nsgif_result nsgif__decode_complex( + struct nsgif_animation *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_result ret = NSGIF_OK; + uint32_t available = 0; + uint8_t step = 24; + uint32_t y = 0; + + if (height == 0) { + return NSGIF_OK; + } + + /* Initialise the LZW decoding */ + res = lzw_decode_init(gif->lzw_ctx, data[0], + gif->nsgif_data, gif->buffer_size, + data + 1 - gif->nsgif_data); + 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->width; + + x = width; + while (x > 0) { + const uint8_t *uncompressed; + unsigned row_available; + if (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); + } + break; + } + res = lzw_decode(gif->lzw_ctx, + &uncompressed, &available); + } + + 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++; + } + } + } + } while (nsgif__next_row(interlace, height, &y, &step)); + + return ret; +} + +static nsgif_result nsgif__decode_simple( + struct nsgif_animation *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->width * height; + uint32_t written = 0; + nsgif_result ret = NSGIF_OK; + lzw_result res; + + /* Initialise the LZW decoding */ + res = lzw_decode_init_map(gif->lzw_ctx, data[0], + transparency_index, colour_table, + gif->nsgif_data, gif->buffer_size, + data + 1 - gif->nsgif_data); + if (res != LZW_OK) { + return nsgif__error_from_lzw(res); + } + + frame_data += (offset_y * gif->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_result nsgif__decode( + struct nsgif_animation *gif, + struct nsgif_frame *frame, + const uint8_t *data, + uint32_t *restrict frame_data) +{ + nsgif_result ret; + uint32_t offset_x = frame->redraw_x; + uint32_t offset_y = frame->redraw_y; + uint32_t width = frame->redraw_width; + uint32_t height = frame->redraw_height; + uint32_t interlace = frame->flags & NSGIF_INTERLACE_MASK; + uint32_t transparency_index = frame->transparency_index; + uint32_t *restrict colour_table = gif->colour_table; + + if (interlace == false && width == gif->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; +} + +/** + * 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_animation *gif, + struct nsgif_frame *frame, + uint32_t *bitmap) +{ + if (frame == NULL) { + memset(bitmap, NSGIF_TRANSPARENT_COLOUR, + gif->width * gif->height * sizeof(*bitmap)); + } else { + uint32_t offset_x = frame->redraw_x; + uint32_t offset_y = frame->redraw_y; + uint32_t width = frame->redraw_width; + uint32_t height = frame->redraw_height; + + if (frame->display == false) { + return; + } + + if (frame->transparency) { + for (uint32_t y = 0; y < height; y++) { + uint32_t *scanline = bitmap + offset_x + + (offset_y + y) * gif->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->width; + for (uint32_t x = 0; x < width; x++) { + scanline[x] = gif->bg_colour; + } + } + } + } +} + +static nsgif_result nsgif__update_bitmap( + struct nsgif_animation *gif, + struct nsgif_frame *frame, + const uint8_t *data, + uint32_t frame_idx) +{ + nsgif_result ret; + uint32_t *bitmap; + + gif->decoded_frame = frame_idx; + + bitmap = nsgif__bitmap_get(gif); + if (bitmap == NULL) { + return NSGIF_INSUFFICIENT_MEMORY; + } + + /* Handle any bitmap clearing/restoration required before decoding this + * frame. */ + if (frame_idx == 0 || gif->decoded_frame == NSGIF_INVALID_FRAME) { + nsgif__restore_bg(gif, NULL, bitmap); + + } else { + struct nsgif_frame *prev = &gif->frames[frame_idx - 1]; + + if (prev->disposal_method == NSGIF_DISPOSAL_RESTORE_BG) { + nsgif__restore_bg(gif, prev, bitmap); + + } else if (prev->disposal_method == NSGIF_DISPOSAL_RESTORE_PREV) { + ret = nsgif__recover_frame(gif, bitmap); + if (ret != NSGIF_OK) { + nsgif__restore_bg(gif, prev, bitmap); + } + } + } + + if (frame->disposal_method == 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 application extension + * + * \param[in] frame The gif object we're decoding. + * \param[in] data The data to decode. + * \param[in] len Byte length of data. + * \return NSGIF_INSUFFICIENT_DATA if more data is needed, + * NSGIF_OK for success. + */ +static nsgif_result nsgif__parse_extension_graphic_control( + struct nsgif_frame *frame, + const uint8_t *data, + size_t len) +{ + /* 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_INSUFFICIENT_DATA; + } + + frame->frame_delay = data[3] | (data[4] << 8); + if (data[2] & NSGIF_TRANSPARENCY_MASK) { + frame->transparency = true; + frame->transparency_index = data[5]; + } + + frame->disposal_method = ((data[2] & NSGIF_DISPOSAL_MASK) >> 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->disposal_method == NSGIF_DISPOSAL_RESTORE_QUIRK) { + frame->disposal_method = 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->disposal_method == NSGIF_DISPOSAL_RESTORE_BG || + frame->disposal_method == NSGIF_DISPOSAL_RESTORE_PREV; + + return NSGIF_OK; +} + +/** + * 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_INSUFFICIENT_DATA if more data is needed, + * NSGIF_OK for success. + */ +static nsgif_result nsgif__parse_extension_application( + struct nsgif_animation *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_INSUFFICIENT_DATA; + } + + if ((data[1] == 0x0b) && + (strncmp((const char *)data + 2, "NETSCAPE2.0", 11) == 0) && + (data[13] == 0x03) && (data[14] == 0x01)) { + gif->loop_count = data[15] | (data[16] << 8); + } + + 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_INSUFFICIENT_DATA if more data is needed, + * NSGIF_OK for success. + */ +static nsgif_result nsgif__parse_frame_extensions( + struct nsgif_animation *gif, + struct nsgif_frame *frame, + const uint8_t **pos, + bool decode) +{ + const uint8_t *nsgif_data = *pos; + const uint8_t *nsgif_end = gif->nsgif_data + gif->buffer_size; + int nsgif_bytes = nsgif_end - nsgif_data; + + /* Initialise the extensions */ + while (nsgif_bytes > 0 && nsgif_data[0] == NSGIF_EXTENSION_INTRODUCER) { + bool block_step = true; + nsgif_result ret; + + nsgif_data++; + nsgif_bytes--; + + if (nsgif_bytes == 0) { + return NSGIF_INSUFFICIENT_DATA; + } + + /* Switch on extension label */ + switch (nsgif_data[0]) { + case NSGIF_EXTENSION_GRAPHIC_CONTROL: + if (decode) { + ret = nsgif__parse_extension_graphic_control( + frame, nsgif_data, nsgif_bytes); + if (ret != NSGIF_OK) { + return ret; + } + } + break; + + case NSGIF_EXTENSION_APPLICATION: + if (decode) { + ret = nsgif__parse_extension_application( + gif, nsgif_data, nsgif_bytes); + if (ret != NSGIF_OK) { + return ret; + } + } + break; + + case NSGIF_EXTENSION_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_INSUFFICIENT_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_INSUFFICIENT_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_result nsgif__parse_image_descriptor( + struct nsgif_animation *gif, + struct nsgif_frame *frame, + const uint8_t **pos, + bool decode) +{ + const uint8_t *data = *pos; + size_t len = gif->nsgif_data + gif->buffer_size - 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_INSUFFICIENT_DATA; + } + + if (decode) { + unsigned x, y, w, h; + + if (data[0] != NSGIF_IMAGE_SEPARATOR) { + return NSGIF_FRAME_DATA_ERROR; + } + + 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->redraw_x = x; + frame->redraw_y = y; + frame->redraw_width = w; + frame->redraw_height = h; + + /* Frame size may have grown. */ + gif->width = (x + w > gif->width ) ? x + w : gif->width; + gif->height = (y + h > gif->height) ? y + h : gif->height; + } + + *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_result nsgif__colour_table_extract( + struct nsgif_animation *gif, + uint32_t *colour_table, + size_t colour_table_entries, + const uint8_t **pos, + bool decode) +{ + const uint8_t *data = *pos; + size_t len = gif->nsgif_data + gif->buffer_size - data; + + if (len < colour_table_entries * 3) { + return NSGIF_INSUFFICIENT_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_result nsgif__parse_colour_table( + struct nsgif_animation *gif, + struct nsgif_frame *frame, + const uint8_t **pos, + bool decode) +{ + nsgif_result 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_result nsgif__parse_image_data( + struct nsgif_animation *gif, + struct nsgif_frame *frame, + const uint8_t **pos, + bool decode) +{ + const uint8_t *data = *pos; + size_t len = gif->nsgif_data + gif->buffer_size - data; + uint32_t frame_idx = frame - gif->frames; + uint8_t minimum_code_size; + nsgif_result 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_INSUFFICIENT_DATA; + } + + minimum_code_size = data[0]; + if (minimum_code_size >= LZW_CODE_MAX) { + return NSGIF_DATA_ERROR; + } + + 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_INSUFFICIENT_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; + } + + gif->frame_count = frame_idx + 1; + gif->frames[frame_idx].display = true; + *pos = data; + + /* Check if we've finished */ + if (len < 1) { + return NSGIF_INSUFFICIENT_DATA; + } else { + if (data[0] == NSGIF_TRAILER) { + return NSGIF_OK; + } + } + + return NSGIF_WORKING; + } + + return ret; +} + +static struct nsgif_frame *nsgif__get_frame( + struct nsgif_animation *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 = false; + frame->transparency_index = NSGIF_NO_TRANSPARENCY; + frame->frame_pointer = gif->buffer_position; + frame->redraw_required = false; + frame->disposal_method = 0; + frame->frame_delay = 100; + frame->display = false; + 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 error code + * - NSGIF_INSUFFICIENT_DATA reached unexpected end of source data. + * - NSGIF_FRAME_DATA_ERROR for GIF frame data error + * - NSGIF_INSUFFICIENT_MEMORY for insufficient memory to process + * - NSGIF_DATA_ERROR for GIF error (invalid frame header) + * - NSGIF_OK for successful decoding + * - NSGIF_WORKING for successful decoding if more frames are expected +*/ +static nsgif_result nsgif__process_frame( + struct nsgif_animation *gif, + uint32_t frame_idx, + bool decode) +{ + nsgif_result 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_INSUFFICIENT_MEMORY; + } + + end = gif->nsgif_data + gif->buffer_size; + + if (decode) { + pos = gif->nsgif_data + frame->frame_pointer; + + /* Ensure this frame is supposed to be decoded */ + if (frame->display == false) { + return NSGIF_OK; + } + + /* Ensure the frame is in range to decode */ + if (frame_idx > gif->frame_count_partial) { + return NSGIF_INSUFFICIENT_DATA; + } + + /* Done if frame is already decoded */ + if ((int)frame_idx == gif->decoded_frame) { + return NSGIF_OK; + } + } else { + pos = (uint8_t *)(gif->nsgif_data + gif->buffer_position); + + /* 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_FRAME_DATA_ERROR; + } + } + + /* Initialise any extensions */ + 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->buffer_position = pos - gif->nsgif_data; + } + + return ret; +} + +/* exported function documented in libnsgif.h */ +void nsgif_create(nsgif_animation *gif, nsgif_bitmap_cb_vt *bitmap) +{ + memset(gif, 0, sizeof(nsgif_animation)); + gif->bitmap = *bitmap; + gif->decoded_frame = NSGIF_INVALID_FRAME; + gif->prev_index = NSGIF_INVALID_FRAME; +} + +/** + * 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_result nsgif__parse_header( + struct nsgif_animation *gif, + const uint8_t **pos, + bool strict) +{ + const uint8_t *data = *pos; + size_t len = gif->nsgif_data + gif->buffer_size - data; + + if (len < 6) { + return NSGIF_INSUFFICIENT_DATA; + } + + if (strncmp((const char *) data, "GIF", 3) != 0) { + return NSGIF_DATA_ERROR; + } + data += 3; + + if (strict == true) { + if ((strncmp((const char *) data, "87a", 3) != 0) && + (strncmp((const char *) data, "89a", 3) != 0)) { + return NSGIF_DATA_ERROR; + } + } + 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_result nsgif__parse_logical_screen_descriptor( + struct nsgif_animation *gif, + const uint8_t **pos) +{ + const uint8_t *data = *pos; + size_t len = gif->nsgif_data + gif->buffer_size - data; + + if (len < 7) { + return NSGIF_INSUFFICIENT_DATA; + } + + gif->width = data[0] | (data[1] << 8); + gif->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->loop_count = 1; + + *pos += 7; + return NSGIF_OK; +} + +/* exported function documented in libnsgif.h */ +nsgif_result nsgif_initialise(nsgif_animation *gif, size_t size, const uint8_t *data) +{ + const uint8_t *nsgif_data; + nsgif_result ret; + + /* Initialize values */ + gif->buffer_size = size; + gif->nsgif_data = data; + + /* Get our current processing position */ + nsgif_data = gif->nsgif_data + gif->buffer_position; + + /* See if we should initialise the GIF */ + if (gif->buffer_position == 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; + gif->local_colour_table = NULL; + gif->global_colour_table = NULL; + + /* The caller may have been lazy and not reset any values */ + gif->frame_count = 0; + gif->frame_count_partial = 0; + gif->decoded_frame = NSGIF_INVALID_FRAME; + + 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->buffer_position = nsgif_data - gif->nsgif_data; + + /* 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->width == 640) && (gif->height == 480)) || + ((gif->width == 640) && (gif->height == 512)) || + ((gif->width == 800) && (gif->height == 600)) || + ((gif->width == 1024) && (gif->height == 768)) || + ((gif->width == 1280) && (gif->height == 1024)) || + ((gif->width == 1600) && (gif->height == 1200)) || + ((gif->width == 0) || (gif->height == 0)) || + ((gif->width > 2048) || (gif->height > 2048))) { + gif->width = 1; + gif->height = 1; + } + + /* Allocate some data irrespective of whether we've got any + * colour tables. We always get the maximum size in case a GIF + * is lying to us. It's far better to give the wrong colours + * than to trample over some memory somewhere. + */ + gif->global_colour_table = calloc(NSGIF_MAX_COLOURS, sizeof(uint32_t)); + gif->local_colour_table = calloc(NSGIF_MAX_COLOURS, sizeof(uint32_t)); + if ((gif->global_colour_table == NULL) || + (gif->local_colour_table == NULL)) { + nsgif_finalise(gif); + return NSGIF_INSUFFICIENT_MEMORY; + } + + /* 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->buffer_size == gif->buffer_position + 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->buffer_position = (nsgif_data - gif->nsgif_data); + } 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; + gif->bg_colour = gif->global_colour_table[bg_idx]; + } else { + gif->bg_colour = gif->global_colour_table[0]; + } + } + + 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); + } + } + + /* Repeatedly try to initialise frames */ + do { + ret = nsgif__process_frame(gif, gif->frame_count, false); + } while (ret == NSGIF_WORKING); + + return ret; +} + +/* exported function documented in libnsgif.h */ +nsgif_result nsgif_decode_frame(nsgif_animation *gif, uint32_t frame) +{ + return nsgif__process_frame(gif, frame, true); +} + +/* exported function documented in libnsgif.h */ +void nsgif_finalise(nsgif_animation *gif) +{ + /* 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->local_colour_table); + gif->local_colour_table = NULL; + free(gif->global_colour_table); + gif->global_colour_table = NULL; + + free(gif->prev_frame); + gif->prev_frame = NULL; + + lzw_context_destroy(gif->lzw_ctx); + gif->lzw_ctx = NULL; +} diff --git a/src/libnsgif.c b/src/libnsgif.c deleted file mode 100644 index 9d77894..0000000 --- a/src/libnsgif.c +++ /dev/null @@ -1,1380 +0,0 @@ -/* - * Copyright 2004 Richard Wilson - * Copyright 2008 Sean Fox - * Copyright 2013-2021 Michael Drake - * - * 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 -#include -#include -#include -#include - -#include "lzw.h" -#include "nsgif.h" - -/** - * - * \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. - */ - -/** Maximum colour table size */ -#define NSGIF_MAX_COLOURS 256 - -/** 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_INVALID_FRAME -1 - -/** Transparent colour */ -#define NSGIF_TRANSPARENT_COLOUR 0x00 - -/** No transparency */ -#define NSGIF_NO_TRANSPARENCY (0xFFFFFFFFu) - -enum nsgif_disposal { - NSGIF_DISPOSAL_UNSPECIFIED, - NSGIF_DISPOSAL_NONE, - NSGIF_DISPOSAL_RESTORE_BG, - NSGIF_DISPOSAL_RESTORE_PREV, - NSGIF_DISPOSAL_RESTORE_QUIRK, /**< Alias for NSGIF_DISPOSAL_RESTORE_PREV. */ -}; - -/* GIF Flags */ -#define NSGIF_INTERLACE_MASK 0x40 -#define NSGIF_COLOUR_TABLE_MASK 0x80 -#define NSGIF_COLOUR_TABLE_SIZE_MASK 0x07 -#define NSGIF_EXTENSION_INTRODUCER 0x21 -#define NSGIF_EXTENSION_GRAPHIC_CONTROL 0xf9 -#define NSGIF_DISPOSAL_MASK 0x1c -#define NSGIF_TRANSPARENCY_MASK 0x01 -#define NSGIF_EXTENSION_COMMENT 0xfe -#define NSGIF_EXTENSION_PLAIN_TEXT 0x01 -#define NSGIF_EXTENSION_APPLICATION 0xff -#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_result nsgif__error_from_lzw(lzw_result l_res) -{ - static const nsgif_result g_res[] = { - [LZW_OK] = NSGIF_OK, - [LZW_OK_EOD] = NSGIF_END_OF_FRAME, - [LZW_NO_MEM] = NSGIF_INSUFFICIENT_MEMORY, - [LZW_NO_DATA] = NSGIF_INSUFFICIENT_DATA, - [LZW_EOI_CODE] = NSGIF_FRAME_DATA_ERROR, - [LZW_BAD_ICODE] = NSGIF_FRAME_DATA_ERROR, - [LZW_BAD_CODE] = NSGIF_FRAME_DATA_ERROR, - }; - 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_INSUFFICIENT_MEMORY for a memory error NSGIF_OK for success - */ -static nsgif_result nsgif__initialise_sprite( - struct nsgif_animation *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_INSUFFICIENT_MEMORY; - } - - 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_animation *gif) -{ - nsgif_result ret; - - /* Make sure we have a buffer to decode to. */ - ret = nsgif__initialise_sprite(gif, gif->width, gif->height); - if (ret != NSGIF_OK) { - return NULL; - } - - /* Get the frame data */ - assert(gif->bitmap.get_buffer); - return (uint32_t *)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_animation *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_animation *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_animation *gif) -{ - if (gif->bitmap.test_opaque) { - return gif->bitmap.test_opaque( - gif->frame_image); - } - - return false; -} - -static void nsgif__record_frame( - struct nsgif_animation *gif, - const uint32_t *bitmap) -{ - bool need_alloc = gif->prev_frame == NULL; - uint32_t *prev_frame; - - if (gif->decoded_frame == NSGIF_INVALID_FRAME || - 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 && - gif->width * gif->height > gif->prev_width * gif->prev_height) { - need_alloc = true; - } - - if (need_alloc) { - prev_frame = realloc(gif->prev_frame, - gif->width * gif->height * 4); - if (prev_frame == NULL) { - return; - } - } else { - prev_frame = gif->prev_frame; - } - - memcpy(prev_frame, bitmap, gif->width * gif->height * 4); - - gif->prev_frame = prev_frame; - gif->prev_width = gif->width; - gif->prev_height = gif->height; - gif->prev_index = gif->decoded_frame; -} - -static nsgif_result nsgif__recover_frame( - const struct nsgif_animation *gif, - uint32_t *bitmap) -{ - const uint32_t *prev_frame = gif->prev_frame; - unsigned height = gif->height < gif->prev_height ? gif->height : gif->prev_height; - unsigned width = gif->width < gif->prev_width ? gif->width : gif->prev_width; - - if (prev_frame == NULL) { - return NSGIF_FRAME_DATA_ERROR; - } - - for (unsigned y = 0; y < height; y++) { - memcpy(bitmap, prev_frame, width * 4); - - bitmap += gif->width; - prev_frame += gif->prev_width; - } - - 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); - } -} - -static nsgif_result nsgif__decode_complex( - struct nsgif_animation *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_result ret = NSGIF_OK; - uint32_t available = 0; - uint8_t step = 24; - uint32_t y = 0; - - if (height == 0) { - return NSGIF_OK; - } - - /* Initialise the LZW decoding */ - res = lzw_decode_init(gif->lzw_ctx, data[0], - gif->nsgif_data, gif->buffer_size, - data + 1 - gif->nsgif_data); - 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->width; - - x = width; - while (x > 0) { - const uint8_t *uncompressed; - unsigned row_available; - if (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); - } - break; - } - res = lzw_decode(gif->lzw_ctx, - &uncompressed, &available); - } - - 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++; - } - } - } - } while (nsgif__next_row(interlace, height, &y, &step)); - - return ret; -} - -static nsgif_result nsgif__decode_simple( - struct nsgif_animation *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->width * height; - uint32_t written = 0; - nsgif_result ret = NSGIF_OK; - lzw_result res; - - /* Initialise the LZW decoding */ - res = lzw_decode_init_map(gif->lzw_ctx, data[0], - transparency_index, colour_table, - gif->nsgif_data, gif->buffer_size, - data + 1 - gif->nsgif_data); - if (res != LZW_OK) { - return nsgif__error_from_lzw(res); - } - - frame_data += (offset_y * gif->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_result nsgif__decode( - struct nsgif_animation *gif, - struct nsgif_frame *frame, - const uint8_t *data, - uint32_t *restrict frame_data) -{ - nsgif_result ret; - uint32_t offset_x = frame->redraw_x; - uint32_t offset_y = frame->redraw_y; - uint32_t width = frame->redraw_width; - uint32_t height = frame->redraw_height; - uint32_t interlace = frame->flags & NSGIF_INTERLACE_MASK; - uint32_t transparency_index = frame->transparency_index; - uint32_t *restrict colour_table = gif->colour_table; - - if (interlace == false && width == gif->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; -} - -/** - * 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_animation *gif, - struct nsgif_frame *frame, - uint32_t *bitmap) -{ - if (frame == NULL) { - memset(bitmap, NSGIF_TRANSPARENT_COLOUR, - gif->width * gif->height * sizeof(*bitmap)); - } else { - uint32_t offset_x = frame->redraw_x; - uint32_t offset_y = frame->redraw_y; - uint32_t width = frame->redraw_width; - uint32_t height = frame->redraw_height; - - if (frame->display == false) { - return; - } - - if (frame->transparency) { - for (uint32_t y = 0; y < height; y++) { - uint32_t *scanline = bitmap + offset_x + - (offset_y + y) * gif->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->width; - for (uint32_t x = 0; x < width; x++) { - scanline[x] = gif->bg_colour; - } - } - } - } -} - -static nsgif_result nsgif__update_bitmap( - struct nsgif_animation *gif, - struct nsgif_frame *frame, - const uint8_t *data, - uint32_t frame_idx) -{ - nsgif_result ret; - uint32_t *bitmap; - - gif->decoded_frame = frame_idx; - - bitmap = nsgif__bitmap_get(gif); - if (bitmap == NULL) { - return NSGIF_INSUFFICIENT_MEMORY; - } - - /* Handle any bitmap clearing/restoration required before decoding this - * frame. */ - if (frame_idx == 0 || gif->decoded_frame == NSGIF_INVALID_FRAME) { - nsgif__restore_bg(gif, NULL, bitmap); - - } else { - struct nsgif_frame *prev = &gif->frames[frame_idx - 1]; - - if (prev->disposal_method == NSGIF_DISPOSAL_RESTORE_BG) { - nsgif__restore_bg(gif, prev, bitmap); - - } else if (prev->disposal_method == NSGIF_DISPOSAL_RESTORE_PREV) { - ret = nsgif__recover_frame(gif, bitmap); - if (ret != NSGIF_OK) { - nsgif__restore_bg(gif, prev, bitmap); - } - } - } - - if (frame->disposal_method == 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 application extension - * - * \param[in] frame The gif object we're decoding. - * \param[in] data The data to decode. - * \param[in] len Byte length of data. - * \return NSGIF_INSUFFICIENT_DATA if more data is needed, - * NSGIF_OK for success. - */ -static nsgif_result nsgif__parse_extension_graphic_control( - struct nsgif_frame *frame, - const uint8_t *data, - size_t len) -{ - /* 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_INSUFFICIENT_DATA; - } - - frame->frame_delay = data[3] | (data[4] << 8); - if (data[2] & NSGIF_TRANSPARENCY_MASK) { - frame->transparency = true; - frame->transparency_index = data[5]; - } - - frame->disposal_method = ((data[2] & NSGIF_DISPOSAL_MASK) >> 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->disposal_method == NSGIF_DISPOSAL_RESTORE_QUIRK) { - frame->disposal_method = 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->disposal_method == NSGIF_DISPOSAL_RESTORE_BG || - frame->disposal_method == NSGIF_DISPOSAL_RESTORE_PREV; - - return NSGIF_OK; -} - -/** - * 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_INSUFFICIENT_DATA if more data is needed, - * NSGIF_OK for success. - */ -static nsgif_result nsgif__parse_extension_application( - struct nsgif_animation *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_INSUFFICIENT_DATA; - } - - if ((data[1] == 0x0b) && - (strncmp((const char *)data + 2, "NETSCAPE2.0", 11) == 0) && - (data[13] == 0x03) && (data[14] == 0x01)) { - gif->loop_count = data[15] | (data[16] << 8); - } - - 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_INSUFFICIENT_DATA if more data is needed, - * NSGIF_OK for success. - */ -static nsgif_result nsgif__parse_frame_extensions( - struct nsgif_animation *gif, - struct nsgif_frame *frame, - const uint8_t **pos, - bool decode) -{ - const uint8_t *nsgif_data = *pos; - const uint8_t *nsgif_end = gif->nsgif_data + gif->buffer_size; - int nsgif_bytes = nsgif_end - nsgif_data; - - /* Initialise the extensions */ - while (nsgif_bytes > 0 && nsgif_data[0] == NSGIF_EXTENSION_INTRODUCER) { - bool block_step = true; - nsgif_result ret; - - nsgif_data++; - nsgif_bytes--; - - if (nsgif_bytes == 0) { - return NSGIF_INSUFFICIENT_DATA; - } - - /* Switch on extension label */ - switch (nsgif_data[0]) { - case NSGIF_EXTENSION_GRAPHIC_CONTROL: - if (decode) { - ret = nsgif__parse_extension_graphic_control( - frame, nsgif_data, nsgif_bytes); - if (ret != NSGIF_OK) { - return ret; - } - } - break; - - case NSGIF_EXTENSION_APPLICATION: - if (decode) { - ret = nsgif__parse_extension_application( - gif, nsgif_data, nsgif_bytes); - if (ret != NSGIF_OK) { - return ret; - } - } - break; - - case NSGIF_EXTENSION_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_INSUFFICIENT_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_INSUFFICIENT_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_result nsgif__parse_image_descriptor( - struct nsgif_animation *gif, - struct nsgif_frame *frame, - const uint8_t **pos, - bool decode) -{ - const uint8_t *data = *pos; - size_t len = gif->nsgif_data + gif->buffer_size - 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_INSUFFICIENT_DATA; - } - - if (decode) { - unsigned x, y, w, h; - - if (data[0] != NSGIF_IMAGE_SEPARATOR) { - return NSGIF_FRAME_DATA_ERROR; - } - - 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->redraw_x = x; - frame->redraw_y = y; - frame->redraw_width = w; - frame->redraw_height = h; - - /* Frame size may have grown. */ - gif->width = (x + w > gif->width ) ? x + w : gif->width; - gif->height = (y + h > gif->height) ? y + h : gif->height; - } - - *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_result nsgif__colour_table_extract( - struct nsgif_animation *gif, - uint32_t *colour_table, - size_t colour_table_entries, - const uint8_t **pos, - bool decode) -{ - const uint8_t *data = *pos; - size_t len = gif->nsgif_data + gif->buffer_size - data; - - if (len < colour_table_entries * 3) { - return NSGIF_INSUFFICIENT_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_result nsgif__parse_colour_table( - struct nsgif_animation *gif, - struct nsgif_frame *frame, - const uint8_t **pos, - bool decode) -{ - nsgif_result 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_result nsgif__parse_image_data( - struct nsgif_animation *gif, - struct nsgif_frame *frame, - const uint8_t **pos, - bool decode) -{ - const uint8_t *data = *pos; - size_t len = gif->nsgif_data + gif->buffer_size - data; - uint32_t frame_idx = frame - gif->frames; - uint8_t minimum_code_size; - nsgif_result 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_INSUFFICIENT_DATA; - } - - minimum_code_size = data[0]; - if (minimum_code_size >= LZW_CODE_MAX) { - return NSGIF_DATA_ERROR; - } - - 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_INSUFFICIENT_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; - } - - gif->frame_count = frame_idx + 1; - gif->frames[frame_idx].display = true; - *pos = data; - - /* Check if we've finished */ - if (len < 1) { - return NSGIF_INSUFFICIENT_DATA; - } else { - if (data[0] == NSGIF_TRAILER) { - return NSGIF_OK; - } - } - - return NSGIF_WORKING; - } - - return ret; -} - -static struct nsgif_frame *nsgif__get_frame( - struct nsgif_animation *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 = false; - frame->transparency_index = NSGIF_NO_TRANSPARENCY; - frame->frame_pointer = gif->buffer_position; - frame->redraw_required = false; - frame->disposal_method = 0; - frame->frame_delay = 100; - frame->display = false; - 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 error code - * - NSGIF_INSUFFICIENT_DATA reached unexpected end of source data. - * - NSGIF_FRAME_DATA_ERROR for GIF frame data error - * - NSGIF_INSUFFICIENT_MEMORY for insufficient memory to process - * - NSGIF_DATA_ERROR for GIF error (invalid frame header) - * - NSGIF_OK for successful decoding - * - NSGIF_WORKING for successful decoding if more frames are expected -*/ -static nsgif_result nsgif__process_frame( - struct nsgif_animation *gif, - uint32_t frame_idx, - bool decode) -{ - nsgif_result 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_INSUFFICIENT_MEMORY; - } - - end = gif->nsgif_data + gif->buffer_size; - - if (decode) { - pos = gif->nsgif_data + frame->frame_pointer; - - /* Ensure this frame is supposed to be decoded */ - if (frame->display == false) { - return NSGIF_OK; - } - - /* Ensure the frame is in range to decode */ - if (frame_idx > gif->frame_count_partial) { - return NSGIF_INSUFFICIENT_DATA; - } - - /* Done if frame is already decoded */ - if ((int)frame_idx == gif->decoded_frame) { - return NSGIF_OK; - } - } else { - pos = (uint8_t *)(gif->nsgif_data + gif->buffer_position); - - /* 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_FRAME_DATA_ERROR; - } - } - - /* Initialise any extensions */ - 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->buffer_position = pos - gif->nsgif_data; - } - - return ret; -} - -/* exported function documented in libnsgif.h */ -void nsgif_create(nsgif_animation *gif, nsgif_bitmap_cb_vt *bitmap) -{ - memset(gif, 0, sizeof(nsgif_animation)); - gif->bitmap = *bitmap; - gif->decoded_frame = NSGIF_INVALID_FRAME; - gif->prev_index = NSGIF_INVALID_FRAME; -} - -/** - * 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_result nsgif__parse_header( - struct nsgif_animation *gif, - const uint8_t **pos, - bool strict) -{ - const uint8_t *data = *pos; - size_t len = gif->nsgif_data + gif->buffer_size - data; - - if (len < 6) { - return NSGIF_INSUFFICIENT_DATA; - } - - if (strncmp((const char *) data, "GIF", 3) != 0) { - return NSGIF_DATA_ERROR; - } - data += 3; - - if (strict == true) { - if ((strncmp((const char *) data, "87a", 3) != 0) && - (strncmp((const char *) data, "89a", 3) != 0)) { - return NSGIF_DATA_ERROR; - } - } - 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_result nsgif__parse_logical_screen_descriptor( - struct nsgif_animation *gif, - const uint8_t **pos) -{ - const uint8_t *data = *pos; - size_t len = gif->nsgif_data + gif->buffer_size - data; - - if (len < 7) { - return NSGIF_INSUFFICIENT_DATA; - } - - gif->width = data[0] | (data[1] << 8); - gif->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->loop_count = 1; - - *pos += 7; - return NSGIF_OK; -} - -/* exported function documented in libnsgif.h */ -nsgif_result nsgif_initialise(nsgif_animation *gif, size_t size, const uint8_t *data) -{ - const uint8_t *nsgif_data; - nsgif_result ret; - - /* Initialize values */ - gif->buffer_size = size; - gif->nsgif_data = data; - - /* Get our current processing position */ - nsgif_data = gif->nsgif_data + gif->buffer_position; - - /* See if we should initialise the GIF */ - if (gif->buffer_position == 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; - gif->local_colour_table = NULL; - gif->global_colour_table = NULL; - - /* The caller may have been lazy and not reset any values */ - gif->frame_count = 0; - gif->frame_count_partial = 0; - gif->decoded_frame = NSGIF_INVALID_FRAME; - - 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->buffer_position = nsgif_data - gif->nsgif_data; - - /* 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->width == 640) && (gif->height == 480)) || - ((gif->width == 640) && (gif->height == 512)) || - ((gif->width == 800) && (gif->height == 600)) || - ((gif->width == 1024) && (gif->height == 768)) || - ((gif->width == 1280) && (gif->height == 1024)) || - ((gif->width == 1600) && (gif->height == 1200)) || - ((gif->width == 0) || (gif->height == 0)) || - ((gif->width > 2048) || (gif->height > 2048))) { - gif->width = 1; - gif->height = 1; - } - - /* Allocate some data irrespective of whether we've got any - * colour tables. We always get the maximum size in case a GIF - * is lying to us. It's far better to give the wrong colours - * than to trample over some memory somewhere. - */ - gif->global_colour_table = calloc(NSGIF_MAX_COLOURS, sizeof(uint32_t)); - gif->local_colour_table = calloc(NSGIF_MAX_COLOURS, sizeof(uint32_t)); - if ((gif->global_colour_table == NULL) || - (gif->local_colour_table == NULL)) { - nsgif_finalise(gif); - return NSGIF_INSUFFICIENT_MEMORY; - } - - /* 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->buffer_size == gif->buffer_position + 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->buffer_position = (nsgif_data - gif->nsgif_data); - } 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; - gif->bg_colour = gif->global_colour_table[bg_idx]; - } else { - gif->bg_colour = gif->global_colour_table[0]; - } - } - - 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); - } - } - - /* Repeatedly try to initialise frames */ - do { - ret = nsgif__process_frame(gif, gif->frame_count, false); - } while (ret == NSGIF_WORKING); - - return ret; -} - -/* exported function documented in libnsgif.h */ -nsgif_result nsgif_decode_frame(nsgif_animation *gif, uint32_t frame) -{ - return nsgif__process_frame(gif, frame, true); -} - -/* exported function documented in libnsgif.h */ -void nsgif_finalise(nsgif_animation *gif) -{ - /* 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->local_colour_table); - gif->local_colour_table = NULL; - free(gif->global_colour_table); - gif->global_colour_table = NULL; - - free(gif->prev_frame); - gif->prev_frame = NULL; - - lzw_context_destroy(gif->lzw_ctx); - gif->lzw_ctx = NULL; -} -- cgit v1.2.3