diff options
author | Michael Drake <tlsa@netsurf-browser.org> | 2021-12-01 14:02:42 +0000 |
---|---|---|
committer | Michael Drake <tlsa@netsurf-browser.org> | 2021-12-01 14:04:51 +0000 |
commit | de0f5828f63d0db82c4157701a763796420eaa80 (patch) | |
tree | 5577cc7010dd83391d321e4b6c1e94c1550d726b /src/gif.c | |
parent | c1a577e96ca0067817dce90a007a66321eae56a8 (diff) | |
download | libnsgif-de0f5828f63d0db82c4157701a763796420eaa80.tar.gz libnsgif-de0f5828f63d0db82c4157701a763796420eaa80.tar.bz2 |
LIB: Rename public header and source file.
This allows the library header to have a name consistent with
the library soname.
Diffstat (limited to 'src/gif.c')
-rw-r--r-- | src/gif.c | 1380 |
1 files changed, 1380 insertions, 0 deletions
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 <richard.wilson@netsurf-browser.org> + * Copyright 2008 Sean Fox <dyntryx@gmail.com> + * Copyright 2013-2021 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" + +/** + * + * \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; +} |