diff options
author | Michael Drake <tlsa@netsurf-browser.org> | 2022-02-22 18:47:19 +0000 |
---|---|---|
committer | Michael Drake <tlsa@netsurf-browser.org> | 2022-02-23 16:13:14 +0000 |
commit | 0526c55f20ee7480533567fad62ea00e6bf31786 (patch) | |
tree | 960a874ba4019027d13bfe73b3476cfa32f38b01 | |
parent | b5dfaef72b2e2725810320cd2cf703463b3e95e1 (diff) | |
download | libnsgif-0526c55f20ee7480533567fad62ea00e6bf31786.tar.gz libnsgif-0526c55f20ee7480533567fad62ea00e6bf31786.tar.bz2 |
API: Rework library API to keep internal workings private.
-rw-r--r-- | include/nsgif.h | 226 | ||||
-rw-r--r-- | src/gif.c | 479 |
2 files changed, 501 insertions, 204 deletions
diff --git a/include/nsgif.h b/include/nsgif.h index 5e69b7e..11378d1 100644 --- a/include/nsgif.h +++ b/include/nsgif.h @@ -18,6 +18,26 @@ #include <stdint.h> #include <stdbool.h> +#include <inttypes.h> + +#define NSGIF_INFINITE (UINT32_MAX) + +typedef struct nsgif nsgif; + +typedef struct nsgif_info { + /** width of GIF (may increase during decoding) */ + uint32_t width; + /** height of GIF (may increase during decoding) */ + uint32_t height; + /** number of frames decoded */ + uint32_t frame_count; + /** number of times to loop animation */ + int loop_max; + /** number of animation loops so far */ + int loop_count; + + uint16_t delay_min; +} nsgif_info_t; /* Error return values */ typedef enum { @@ -29,51 +49,30 @@ typedef enum { NSGIF_DATA_ERROR = -4, NSGIF_INSUFFICIENT_MEMORY = -5, NSGIF_FRAME_NO_DISPLAY = -6, - NSGIF_END_OF_FRAME = -7 + NSGIF_END_OF_FRAME = -7, + NSGIF_FRAME_INVALID = -8, + NSGIF_ANIMATION_COMPLETE = -9, } nsgif_result; -/** GIF rectangle structure. */ +/** + * GIF rectangle structure. + * + * * Top left coordinate is `(x0, y0)`. + * * Width is `x1 - x0`. + * * Height is `y1 - y0`. + * * Units are pixels. + */ typedef struct nsgif_rect { - /** x co-ordinate of redraw rectangle */ - uint32_t x; - /** y co-ordinate of redraw rectangle */ - uint32_t y; - /** width of redraw rectangle */ - uint32_t w; - /** height of redraw rectangle */ - uint32_t h; + /** x co-ordinate of redraw rectangle, left */ + uint32_t x0; + /** y co-ordinate of redraw rectangle, top */ + uint32_t y0; + /** x co-ordinate of redraw rectangle, right */ + uint32_t x1; + /** y co-ordinate of redraw rectangle, bottom */ + uint32_t y1; } nsgif_rect; -/** 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; - /* Frame flags */ - uint32_t flags; - - /** Frame's redraw rectangle. */ - nsgif_rect redraw; -} nsgif_frame; - /* API for Bitmap callbacks */ typedef void* (*nsgif_bitmap_cb_create)(int width, int height); typedef void (*nsgif_bitmap_cb_destroy)(void *bitmap); @@ -101,75 +100,36 @@ typedef struct nsgif_bitmap_cb_vt { nsgif_bitmap_cb_modified modified; } nsgif_bitmap_cb_vt; -/** GIF animation data */ -typedef struct nsgif { - /** 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; - /** - * Initialises necessary nsgif members. + * Create the NSGIF object. + * + * \param[in] bitmap_vt Bitmap operation functions v-table. + * \param[out] gif_out Return NSGIF object on success. + * \return NSGIF_OK on success, or appropriate error otherwise. */ -void nsgif_create(nsgif *gif, nsgif_bitmap_cb_vt *bitmap_callbacks); +nsgif_result nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif **gif_out); /** - * 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. + * Scan the source image data. + * + * This is used to feed the source data into LibNSGIF. This must be called + * before calling \ref nsgif_frame_decode. + * + * It can be called multiple times with, with increasing sizes. If it is called + * several times, as more data is available (e.g. slow network fetch) the data + * already given to \ref nsgif_data_scan must be provided each time. + * + * For example, if you call \ref nsgif_data_scan with 25 bytes of data, and then + * fetch another 10 bytes, you would need to call \ref nsgif_data with a size of + * 35 bytes, and the whole 35 bytes must be contiguous memory. It is safe to + * `realloc` the source buffer between calls to \ref nsgif_data_scan. (The + * actual data pointer is allowed to be different.) + * + * If an error occurs, all previously scanned frames are retained. + * + * \param[in] gif The NSGIF object. + * \param[in] size Number of bytes in data. + * \param[in] data Raw source GIF data. * * \return Error return value. * - NSGIF_FRAME_DATA_ERROR for GIF frame data error @@ -179,11 +139,30 @@ void nsgif_create(nsgif *gif, nsgif_bitmap_cb_vt *bitmap_callbacks); * - NSGIF_OK for successful decoding * - NSGIF_WORKING for successful decoding if more frames are expected */ -nsgif_result nsgif_initialise(nsgif *gif, size_t size, const uint8_t *data); +nsgif_result nsgif_data_scan( + nsgif *gif, + size_t size, + const uint8_t *data); + +/** + * Prepare to show a frame. + * + * \param[in] gif The NSGIF object. + * \param[out] area The area in pixels that must be redrawn. + * \param[out] delay_cs Time to wait after frame_new before next frame in cs. + * \param[out] frame_new The frame to decode. + */ +nsgif_result nsgif_frame_prepare( + nsgif *gif, + nsgif_rect *area, + uint32_t *delay_cs, + uint32_t *frame_new); /** * Decodes a GIF frame. * + * \param[in] gif The nsgif object. + * \param[in] frame The frame number to decode. * \return Error return value. * - NSGIF_FRAME_DATA_ERROR for GIF frame data error * - NSGIF_DATA_ERROR for GIF error (invalid frame header) @@ -191,11 +170,40 @@ nsgif_result nsgif_initialise(nsgif *gif, size_t size, const uint8_t *data); * - NSGIF_INSUFFICIENT_MEMORY for insufficient memory to process * - NSGIF_OK for successful decoding */ -nsgif_result nsgif_decode_frame(nsgif *gif, uint32_t frame); +nsgif_result nsgif_frame_decode( + nsgif *gif, + uint32_t frame, + const uint32_t **buffer); /** - * Releases any workspace held by a gif + * Reset a GIF animation. + * + * Some animations are only meant to loop N times, and then show the + * final frame forever. This function resets the loop and frame counters, + * so that the animation can be replayed without the overhead of recreating + * the NSGIF object and rescanning the raw data. + * + * \param[in] gif A NSGIF object. + * + * \return NSGIF_OK on success, or appropriate error otherwise. + */ +nsgif_result nsgif_reset( + nsgif *gif); + +/** + * Get information about a GIF from an NSGIF object. + * + * \param[in] gif The NSGIF object to get info for. + * + * \return The gif info, or NULL on error. + */ +const nsgif_info_t *nsgif_get_info(const nsgif *gif); + +/** + * Free a NSGIF object. + * + * \param[in] gif The NSGIF to free. */ -void nsgif_finalise(nsgif *gif); +void nsgif_destroy(nsgif *gif); #endif @@ -17,6 +17,92 @@ #include "lzw.h" #include "nsgif.h" +/** 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; + /* Frame flags */ + uint32_t flags; + + /** Frame's redraw rectangle. */ + nsgif_rect redraw; +} nsgif_frame; + +/** GIF animation data */ +struct nsgif { + struct nsgif_info info; + + /** LZW decode context */ + void *lzw_ctx; + /** callbacks for bitmap functions */ + nsgif_bitmap_cb_vt bitmap; + /** pointer to GIF data */ + const uint8_t *nsgif_data; + /** decoded frames */ + nsgif_frame *frames; + /** current frame */ + uint32_t frame; + /** current frame decoded to bitmap */ + uint32_t decoded_frame; + /** currently decoded image; stored as bitmap from bitmap_create callback */ + void *frame_image; + + uint16_t delay_default; + /** number of frames partially decoded */ + uint32_t frame_count_partial; + + /** 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 */ + uint32_t prev_index; + /** previous frame width */ + uint32_t prev_width; + /** previous frame height */ + uint32_t prev_height; +}; + /** * * \file @@ -35,7 +121,7 @@ #define NSGIF_PROCESS_COLOURS 0xaa000000 /** Internal flag that a frame is invalid/unprocessed */ -#define NSGIF_INVALID_FRAME -1 +#define NSGIF_FRAME_INVALID UINT32_MAX /** Transparent colour */ #define NSGIF_TRANSPARENT_COLOUR 0x00 @@ -118,7 +204,7 @@ static inline uint32_t* nsgif__bitmap_get( nsgif_result ret; /* Make sure we have a buffer to decode to. */ - ret = nsgif__initialise_sprite(gif, gif->width, gif->height); + ret = nsgif__initialise_sprite(gif, gif->info.width, gif->info.height); if (ret != NSGIF_OK) { return NULL; } @@ -183,7 +269,7 @@ static void nsgif__record_frame( bool need_alloc = gif->prev_frame == NULL; uint32_t *prev_frame; - if (gif->decoded_frame == NSGIF_INVALID_FRAME || + if (gif->decoded_frame == NSGIF_FRAME_INVALID || gif->decoded_frame == gif->prev_index) { /* No frame to copy, or already have this frame recorded. */ return; @@ -195,13 +281,13 @@ static void nsgif__record_frame( } if (gif->prev_frame != NULL && - gif->width * gif->height > gif->prev_width * gif->prev_height) { + gif->info.width * gif->info.height > gif->prev_width * gif->prev_height) { need_alloc = true; } if (need_alloc) { prev_frame = realloc(gif->prev_frame, - gif->width * gif->height * 4); + gif->info.width * gif->info.height * 4); if (prev_frame == NULL) { return; } @@ -209,11 +295,11 @@ static void nsgif__record_frame( prev_frame = gif->prev_frame; } - memcpy(prev_frame, bitmap, gif->width * gif->height * 4); + memcpy(prev_frame, bitmap, gif->info.width * gif->info.height * 4); gif->prev_frame = prev_frame; - gif->prev_width = gif->width; - gif->prev_height = gif->height; + gif->prev_width = gif->info.width; + gif->prev_height = gif->info.height; gif->prev_index = gif->decoded_frame; } @@ -222,8 +308,8 @@ static nsgif_result nsgif__recover_frame( 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; + unsigned height = gif->info.height < gif->prev_height ? gif->info.height : gif->prev_height; + unsigned width = gif->info.width < gif->prev_width ? gif->info.width : gif->prev_width; if (prev_frame == NULL) { return NSGIF_FRAME_DATA_ERROR; @@ -232,7 +318,7 @@ static nsgif_result nsgif__recover_frame( for (unsigned y = 0; y < height; y++) { memcpy(bitmap, prev_frame, width * 4); - bitmap += gif->width; + bitmap += gif->info.width; prev_frame += gif->prev_width; } @@ -347,16 +433,16 @@ static nsgif_result nsgif__decode_complex( { lzw_result res; nsgif_result ret = NSGIF_OK; - uint32_t clip_x = gif__clip(offset_x, width, gif->width); - uint32_t clip_y = gif__clip(offset_y, height, gif->height); + uint32_t clip_x = gif__clip(offset_x, width, gif->info.width); + uint32_t clip_y = gif__clip(offset_y, height, gif->info.height); const uint8_t *uncompressed; uint32_t available = 0; uint8_t step = 24; uint32_t skip = 0; uint32_t y = 0; - if (offset_x >= gif->width || - offset_y >= gif->height) { + if (offset_x >= gif->info.width || + offset_y >= gif->info.height) { return NSGIF_OK; } @@ -380,7 +466,7 @@ static nsgif_result nsgif__decode_complex( uint32_t *frame_scanline; frame_scanline = frame_data + offset_x + - (y + offset_y) * gif->width; + (y + offset_y) * gif->info.width; x = width; while (x > 0) { @@ -441,16 +527,16 @@ static nsgif_result nsgif__decode_simple( uint32_t *restrict frame_data, uint32_t *restrict colour_table) { - uint32_t pixels = gif->width * height; + uint32_t pixels = gif->info.width * height; uint32_t written = 0; nsgif_result ret = NSGIF_OK; lzw_result res; - if (offset_y >= gif->height) { + if (offset_y >= gif->info.height) { return NSGIF_OK; } - height -= gif__clip(offset_y, height, gif->height); + height -= gif__clip(offset_y, height, gif->info.height); if (height == 0) { return NSGIF_OK; @@ -465,7 +551,7 @@ static nsgif_result nsgif__decode_simple( return nsgif__error_from_lzw(res); } - frame_data += (offset_y * gif->width); + frame_data += (offset_y * gif->info.width); while (pixels > 0) { res = lzw_decode_map(gif->lzw_ctx, @@ -501,15 +587,15 @@ static inline nsgif_result nsgif__decode( }; nsgif_result ret; - uint32_t width = frame->redraw.w; - uint32_t height = frame->redraw.h; - uint32_t offset_x = frame->redraw.x; - uint32_t offset_y = frame->redraw.y; + uint32_t width = frame->redraw.x1 - frame->redraw.x0; + uint32_t height = frame->redraw.y1 - frame->redraw.y0; + uint32_t offset_x = frame->redraw.x0; + uint32_t offset_y = frame->redraw.y0; uint32_t interlace = frame->flags & GIF_MASK_INTERLACE; uint32_t transparency_index = frame->transparency_index; uint32_t *restrict colour_table = gif->colour_table; - if (interlace == false && width == gif->width && offset_x == 0) { + if (interlace == false && width == gif->info.width && offset_x == 0) { ret = nsgif__decode_simple(gif, height, offset_y, data, transparency_index, frame_data, colour_table); @@ -537,15 +623,15 @@ static void nsgif__restore_bg( { if (frame == NULL) { memset(bitmap, NSGIF_TRANSPARENT_COLOUR, - gif->width * gif->height * sizeof(*bitmap)); + gif->info.width * gif->info.height * sizeof(*bitmap)); } else { - uint32_t width = frame->redraw.w; - uint32_t height = frame->redraw.h; - uint32_t offset_x = frame->redraw.x; - uint32_t offset_y = frame->redraw.y; + uint32_t width = frame->redraw.x1 - frame->redraw.x0; + uint32_t height = frame->redraw.y1 - frame->redraw.y0; + uint32_t offset_x = frame->redraw.x0; + uint32_t offset_y = frame->redraw.y0; - width -= gif__clip(offset_x, width, gif->width); - height -= gif__clip(offset_y, height, gif->height); + width -= gif__clip(offset_x, width, gif->info.width); + height -= gif__clip(offset_y, height, gif->info.height); if (frame->display == false || width == 0) { return; @@ -554,14 +640,14 @@ static void nsgif__restore_bg( if (frame->transparency) { for (uint32_t y = 0; y < height; y++) { uint32_t *scanline = bitmap + offset_x + - (offset_y + y) * gif->width; + (offset_y + y) * gif->info.width; memset(scanline, NSGIF_TRANSPARENT_COLOUR, width * sizeof(*bitmap)); } } else { for (uint32_t y = 0; y < height; y++) { uint32_t *scanline = bitmap + offset_x + - (offset_y + y) * gif->width; + (offset_y + y) * gif->info.width; for (uint32_t x = 0; x < width; x++) { scanline[x] = gif->bg_colour; } @@ -588,7 +674,7 @@ static nsgif_result nsgif__update_bitmap( /* Handle any bitmap clearing/restoration required before decoding this * frame. */ - if (frame_idx == 0 || gif->decoded_frame == NSGIF_INVALID_FRAME) { + if (frame_idx == 0 || gif->decoded_frame == NSGIF_FRAME_INVALID) { nsgif__restore_bg(gif, NULL, bitmap); } else { @@ -626,6 +712,7 @@ static nsgif_result nsgif__update_bitmap( /** * Parse the graphic control extension * + * \param[in] gif The gif object we're decoding. * \param[in] frame The gif object we're decoding. * \param[in] data The data to decode. * \param[in] len Byte length of data. @@ -633,6 +720,7 @@ static nsgif_result nsgif__update_bitmap( * NSGIF_OK for success. */ static nsgif_result nsgif__parse_extension_graphic_control( + const struct nsgif *gif, struct nsgif_frame *frame, const uint8_t *data, size_t len) @@ -659,6 +747,10 @@ static nsgif_result nsgif__parse_extension_graphic_control( } frame->frame_delay = data[3] | (data[4] << 8); + if (frame->frame_delay < gif->info.delay_min) { + frame->frame_delay = gif->delay_default; + } + if (data[2] & GIF_MASK_TRANSPARENCY) { frame->transparency = true; frame->transparency_index = data[5]; @@ -713,7 +805,7 @@ static nsgif_result nsgif__parse_extension_application( 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); + gif->info.loop_max = data[15] | (data[16] << 8); } return NSGIF_OK; @@ -763,7 +855,9 @@ static nsgif_result nsgif__parse_frame_extensions( case GIF_EXT_GRAPHIC_CONTROL: if (decode) { ret = nsgif__parse_extension_graphic_control( - frame, nsgif_data, nsgif_bytes); + gif, frame, + nsgif_data, + nsgif_bytes); if (ret != NSGIF_OK) { return ret; } @@ -867,7 +961,7 @@ static nsgif_result nsgif__parse_image_descriptor( } if (decode) { - unsigned x, y, w, h; + uint32_t x, y, w, h; if (data[0] != NSGIF_IMAGE_SEPARATOR) { return NSGIF_FRAME_DATA_ERROR; @@ -879,18 +973,18 @@ static nsgif_result nsgif__parse_image_descriptor( h = data[7] | (data[8] << 8); frame->flags = data[9]; - frame->redraw.x = x; - frame->redraw.y = y; - frame->redraw.w = w; - frame->redraw.h = h; + frame->redraw.x0 = x; + frame->redraw.y0 = y; + frame->redraw.x1 = x + w; + frame->redraw.y1 = y + h; /* Allow first frame to grow image dimensions. */ - if (gif->frame_count == 0) { - if (x + w > gif->width) { - gif->width = x + w; + if (gif->info.frame_count == 0) { + if (x + w > gif->info.width) { + gif->info.width = x + w; } - if (y + h > gif->height) { - gif->height = y + h; + if (y + h > gif->info.height) { + gif->info.height = y + h; } } } @@ -1045,7 +1139,7 @@ static nsgif_result nsgif__parse_image_data( 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 */ + /* Check if the frame data runs off the end of the file */ if (block_size > len) { block_size = len; return NSGIF_OK; @@ -1055,7 +1149,7 @@ static nsgif_result nsgif__parse_image_data( data += block_size; } - gif->frame_count = frame_idx + 1; + gif->info.frame_count = frame_idx + 1; gif->frames[frame_idx].display = true; *pos = data; @@ -1154,7 +1248,7 @@ static nsgif_result nsgif__process_frame( } /* Done if frame is already decoded */ - if ((int)frame_idx == gif->decoded_frame) { + if (frame_idx == gif->decoded_frame) { return NSGIF_OK; } } else { @@ -1201,13 +1295,57 @@ cleanup: return ret; } -/* exported function documented in libnsgif.h */ -void nsgif_create(nsgif *gif, nsgif_bitmap_cb_vt *bitmap) +/* exported function documented in nsgif.h */ +void nsgif_destroy(nsgif *gif) { - memset(gif, 0, sizeof(nsgif)); - gif->bitmap = *bitmap; - gif->decoded_frame = NSGIF_INVALID_FRAME; - gif->prev_index = NSGIF_INVALID_FRAME; + if (gif == NULL) { + return; + } + + /* Release all our memory blocks */ + if (gif->frame_image) { + assert(gif->bitmap.destroy); + gif->bitmap.destroy(gif->frame_image); + gif->frame_image = NULL; + } + + free(gif->frames); + gif->frames = NULL; + + free(gif->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; + + free(gif); +} + +/* exported function documented in nsgif.h */ +nsgif_result nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif **gif_out) +{ + nsgif *gif; + + gif = calloc(1, sizeof(*gif)); + if (gif == NULL) { + return NSGIF_INSUFFICIENT_MEMORY; + } + + gif->bitmap = *bitmap_vt; + gif->decoded_frame = NSGIF_FRAME_INVALID; + gif->prev_index = NSGIF_FRAME_INVALID; + + gif->info.delay_min = 2; + gif->delay_default = 10; + + *gif_out = gif; + return NSGIF_OK; } /** @@ -1282,20 +1420,23 @@ static nsgif_result nsgif__parse_logical_screen_descriptor( return NSGIF_INSUFFICIENT_DATA; } - gif->width = data[0] | (data[1] << 8); - gif->height = data[2] | (data[3] << 8); + gif->info.width = data[0] | (data[1] << 8); + gif->info.height = data[2] | (data[3] << 8); gif->global_colours = data[4] & NSGIF_COLOUR_TABLE_MASK; gif->colour_table_size = 2 << (data[4] & NSGIF_COLOUR_TABLE_SIZE_MASK); gif->bg_index = data[5]; gif->aspect_ratio = data[6]; - gif->loop_count = 1; + gif->info.loop_max = 1; *pos += 7; return NSGIF_OK; } -/* exported function documented in libnsgif.h */ -nsgif_result nsgif_initialise(nsgif *gif, size_t size, const uint8_t *data) +/* exported function documented in nsgif.h */ +nsgif_result nsgif_data_scan( + nsgif *gif, + size_t size, + const uint8_t *data) { const uint8_t *nsgif_data; nsgif_result ret; @@ -1319,9 +1460,10 @@ nsgif_result nsgif_initialise(nsgif *gif, size_t size, const uint8_t *data) gif->global_colour_table = NULL; /* The caller may have been lazy and not reset any values */ - gif->frame_count = 0; + gif->info.frame_count = 0; gif->frame_count_partial = 0; - gif->decoded_frame = NSGIF_INVALID_FRAME; + gif->decoded_frame = NSGIF_FRAME_INVALID; + gif->frame = NSGIF_FRAME_INVALID; ret = nsgif__parse_header(gif, &nsgif_data, false); if (ret != NSGIF_OK) { @@ -1341,16 +1483,16 @@ nsgif_result nsgif_initialise(nsgif *gif, size_t size, const uint8_t *data) * 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; + if (((gif->info.width == 640) && (gif->info.height == 480)) || + ((gif->info.width == 640) && (gif->info.height == 512)) || + ((gif->info.width == 800) && (gif->info.height == 600)) || + ((gif->info.width == 1024) && (gif->info.height == 768)) || + ((gif->info.width == 1280) && (gif->info.height == 1024)) || + ((gif->info.width == 1600) && (gif->info.height == 1200)) || + ((gif->info.width == 0) || (gif->info.height == 0)) || + ((gif->info.width > 2048) || (gif->info.height > 2048))) { + gif->info.width = 1; + gif->info.height = 1; } /* Allocate some data irrespective of whether we've got any @@ -1362,7 +1504,6 @@ nsgif_result nsgif_initialise(nsgif *gif, size_t size, const uint8_t *data) 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; } @@ -1430,38 +1571,186 @@ nsgif_result nsgif_initialise(nsgif *gif, size_t size, const uint8_t *data) /* Repeatedly try to initialise frames */ do { - ret = nsgif__process_frame(gif, gif->frame_count, false); + ret = nsgif__process_frame(gif, gif->info.frame_count, false); } while (ret == NSGIF_WORKING); return ret; } -/* exported function documented in libnsgif.h */ -nsgif_result nsgif_decode_frame(nsgif *gif, uint32_t frame) +static void nsgif__redraw_rect_extend(const nsgif_rect *frame, nsgif_rect *redraw) { - return nsgif__process_frame(gif, frame, true); + if (redraw->x1 == 0 || redraw->y1 == 0) { + *redraw = *frame; + } else { + if (redraw->x0 > frame->x0) { + redraw->x0 = frame->x0; + } + if (redraw->x1 < frame->x1) { + redraw->x1 = frame->x1; + } + if (redraw->y0 > frame->y0) { + redraw->y0 = frame->y0; + } + if (redraw->y1 < frame->y1) { + redraw->y1 = frame->y1; + } + } } -/* exported function documented in libnsgif.h */ -void nsgif_finalise(nsgif *gif) +static uint32_t nsgif__frame_next( + nsgif *gif, + bool partial, + uint32_t frame) { - /* Release all our memory blocks */ - if (gif->frame_image) { - assert(gif->bitmap.destroy); - gif->bitmap.destroy(gif->frame_image); + uint32_t frames = partial ? + gif->frame_count_partial : + gif->info.frame_count; + + if (frames == 0) { + return NSGIF_FRAME_INVALID; } - 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; + frame++; + return (frame >= frames) ? 0 : frame; +} - free(gif->prev_frame); - gif->prev_frame = NULL; +static nsgif_result nsgif__next_displayable_frame( + nsgif *gif, + uint32_t *frame, + uint32_t *delay) +{ + uint32_t next = *frame; - lzw_context_destroy(gif->lzw_ctx); - gif->lzw_ctx = NULL; + do { + next = nsgif__frame_next(gif, false, next); + if (next == *frame || next == NSGIF_FRAME_INVALID) { + return NSGIF_FRAME_NO_DISPLAY; + } + + if (delay != NULL) { + *delay += gif->frames[next].frame_delay; + } + + } while (gif->frames[next].display == false); + + *frame = next; + return NSGIF_OK; +} + +static inline bool nsgif__animation_complete(int count, int max) +{ + if (max == 0) { + return false; + } + + return (count >= max); +} + +nsgif_result nsgif_reset( + nsgif *gif) +{ + gif->info.loop_count = 0; + gif->frame = NSGIF_FRAME_INVALID; + + return NSGIF_OK; +} + +/* exported function documented in nsgif.h */ +nsgif_result nsgif_frame_prepare( + nsgif *gif, + nsgif_rect *area, + uint32_t *delay_cs, + uint32_t *frame_new) +{ + nsgif_result ret; + nsgif_rect rect = { + .x1 = 0, + .y1 = 0, + }; + uint32_t delay = 0; + uint32_t frame = gif->frame; + uint32_t frame_next; + + if (gif->frame != NSGIF_FRAME_INVALID && + gif->frame != 0 && + gif->frame < gif->info.frame_count && + gif->frames[gif->frame].display) { + rect = gif->frames[gif->frame].redraw; + } + + if (nsgif__animation_complete( + gif->info.loop_count, + gif->info.loop_max)) { + return NSGIF_ANIMATION_COMPLETE; + } + + ret = nsgif__next_displayable_frame(gif, &frame, NULL); + if (ret != NSGIF_OK) { + return ret; + } + + if (gif->frame != NSGIF_FRAME_INVALID && frame < gif->frame) { + gif->info.loop_count++; + } + + frame_next = frame; + ret = nsgif__next_displayable_frame(gif, &frame_next, &delay); + if (ret != NSGIF_OK) { + return ret; + } + + if (frame_next < frame) { + if (nsgif__animation_complete( + gif->info.loop_count + 1, + gif->info.loop_max)) { + delay = NSGIF_INFINITE; + } + } + + gif->frame = frame; + nsgif__redraw_rect_extend(&gif->frames[frame].redraw, &rect); + + *frame_new = gif->frame; + *delay_cs = delay; + *area = rect; + + return NSGIF_OK; +} + +/* exported function documented in nsgif.h */ +nsgif_result nsgif_frame_decode( + nsgif *gif, + uint32_t frame, + const uint32_t **buffer) +{ + uint32_t start_frame; + nsgif_result ret = NSGIF_OK; + + if (gif->decoded_frame == frame) { + *buffer = gif->frame_image; + return NSGIF_OK; + + } else if (gif->decoded_frame >= frame || + gif->decoded_frame == NSGIF_FRAME_INVALID) { + /* Can skip to first frame or restart. */ + start_frame = 0; + } else { + start_frame = nsgif__frame_next( + gif, false, gif->decoded_frame); + } + + for (uint32_t f = start_frame; f <= frame; f++) { + ret = nsgif__process_frame(gif, f, true); + if (ret != NSGIF_OK) { + return ret; + } + } + + *buffer = gif->frame_image; + return ret; +} + +const nsgif_info_t *nsgif_get_info(const nsgif *gif) +{ + return &gif->info; } |