From f429a4f329cfaebc3e6644cac9610b89c74d9ba2 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Thu, 3 Nov 2022 17:35:24 +0000 Subject: API: Add call to indicate input data is complete By distinguising between waiting for more data and a broken truncated file, we can decode what we can of any final truncated frame. --- include/nsgif.h | 29 +++++++++++++++++ src/gif.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 107 insertions(+), 19 deletions(-) diff --git a/include/nsgif.h b/include/nsgif.h index 208bc27..7e6aa83 100644 --- a/include/nsgif.h +++ b/include/nsgif.h @@ -89,6 +89,11 @@ typedef enum { */ NSGIF_ERR_END_OF_DATA, + /** + * Can't supply more data after calling \ref nsgif_data_complete. + */ + NSGIF_ERR_DATA_COMPLETE, + /** * The current frame cannot be displayed. */ @@ -277,6 +282,8 @@ void nsgif_destroy(nsgif_t *gif); * 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. * + * Once all the data has been provided, call \ref nsgif_data_complete. + * * For example, if you call \ref nsgif_data_scan with 25 bytes of data, and then * fetch another 10 bytes, you would need to call \ref nsgif_data_scan with a * size of 35 bytes, and the whole 35 bytes must be contiguous memory. It is @@ -299,6 +306,24 @@ nsgif_error nsgif_data_scan( size_t size, const uint8_t *data); +/** + * Tell libnsgif that all the gif data has been provided. + * + * Call this after calling \ref nsgif_data_scan with the the entire GIF + * source data. You can call \ref nsgif_data_scan multiple times up until + * this is called, and after this is called, \ref nsgif_data_scan will + * return an error. + * + * You can decode a GIF before this is called, however, it will fail to + * decode any truncated final frame data and will not perform loops when + * driven via \ref nsgif_frame_prepare (because it doesn't know if there + * will be more frames supplied in future data). + * + * \param[in] gif The \ref nsgif_t object. + */ +void nsgif_data_complete( + nsgif_t *gif); + /** * Prepare to show a frame. * @@ -306,6 +331,10 @@ nsgif_error nsgif_data_scan( * returned `delay_cs` will be \ref NSGIF_INFINITE, indicating that the frame * should be shown forever. * + * Note that if \ref nsgif_data_complete has not been called on this gif, + * animated GIFs will not loop back to the start. Instead it will return + * \ref NSGIF_ERR_END_OF_DATA. + * * \param[in] gif The \ref nsgif_t object. * \param[out] area The area in pixels that must be redrawn. * \param[out] delay_cs Time to wait after frame_new before next frame in cs. diff --git a/src/gif.c b/src/gif.c index a5c44ee..2dc631e 100644 --- a/src/gif.c +++ b/src/gif.c @@ -40,6 +40,9 @@ typedef struct nsgif_frame { /** whether a full image redraw is required */ bool redraw_required; + /** Amount of LZW data found in scan */ + uint32_t lzw_data_length; + /** the index designating a transparent pixel */ uint32_t transparency_index; @@ -90,6 +93,12 @@ struct nsgif { /** number of frames partially decoded */ uint32_t frame_count_partial; + /** + * Whether all the GIF data has been supplied, or if there may be + * more to come. + */ + bool data_complete; + /** pointer to GIF data */ const uint8_t *buf; /** current index into GIF data */ @@ -613,6 +622,11 @@ static inline nsgif_error nsgif__decode( frame_data, colour_table); } + if (gif->data_complete && ret == NSGIF_ERR_END_OF_DATA) { + /* This is all the data there is, so make do. */ + ret = NSGIF_OK; + } + return ret; } @@ -1213,16 +1227,19 @@ static nsgif_error nsgif__parse_image_data( len--; while (block_size != 1) { - if (len < 1) return NSGIF_ERR_END_OF_DATA; + if (len < 1) { + return NSGIF_ERR_END_OF_DATA; + } block_size = data[0] + 1; /* Check if the frame data runs off the end of the file */ if (block_size > len) { - block_size = len; + frame->lzw_data_length += len; return NSGIF_OK; } len -= block_size; data += block_size; + frame->lzw_data_length += block_size; } *pos = data; @@ -1258,14 +1275,16 @@ static struct nsgif_frame *nsgif__get_frame( frame = &gif->frames[frame_idx]; - frame->transparency_index = NSGIF_NO_TRANSPARENCY; - frame->frame_offset = gif->buf_pos; frame->info.local_palette = false; frame->info.transparency = false; - frame->redraw_required = false; frame->info.display = false; frame->info.disposal = 0; frame->info.delay = 10; + + frame->transparency_index = NSGIF_NO_TRANSPARENCY; + frame->frame_offset = gif->buf_pos; + frame->redraw_required = false; + frame->lzw_data_length = 0; frame->decoded = false; } @@ -1593,6 +1612,10 @@ nsgif_error nsgif_data_scan( nsgif_error ret; uint32_t frames; + if (gif->data_complete) { + return NSGIF_ERR_DATA_COMPLETE; + } + /* Initialize values */ gif->buf_len = size; gif->buf = data; @@ -1734,6 +1757,32 @@ nsgif_error nsgif_data_scan( return ret; } +/* exported function documented in nsgif.h */ +void nsgif_data_complete( + nsgif_t *gif) +{ + if (gif->data_complete == false) { + uint32_t start = gif->info.frame_count; + uint32_t end = gif->frame_count_partial; + + for (uint32_t f = start; f < end; f++) { + nsgif_frame *frame = &gif->frames[f]; + + if (frame->lzw_data_length > 0) { + frame->info.display = true; + gif->info.frame_count = f + 1; + + if (f == 0) { + frame->info.transparency = true; + } + break; + } + } + } + + gif->data_complete = true; +} + static void nsgif__redraw_rect_extend( const nsgif_rect_t *frame, nsgif_rect_t *redraw) @@ -1782,7 +1831,11 @@ static nsgif_error nsgif__next_displayable_frame( do { next = nsgif__frame_next(gif, false, next); - if (next == *frame || next == NSGIF_FRAME_INVALID) { + if (next <= *frame && *frame != NSGIF_FRAME_INVALID && + gif->data_complete == false) { + return NSGIF_ERR_END_OF_DATA; + + } else if (next == *frame || next == NSGIF_FRAME_INVALID) { return NSGIF_ERR_FRAME_DISPLAY; } @@ -1850,21 +1903,26 @@ nsgif_error nsgif_frame_prepare( gif->loop_count++; } - if (gif->info.frame_count == 1) { - delay = NSGIF_INFINITE; + if (gif->data_complete) { + /* Check for last frame, which has infinite delay. */ - } else if (gif->info.loop_max != 0) { - uint32_t frame_next = frame; - ret = nsgif__next_displayable_frame(gif, &frame_next, NULL); - if (ret != NSGIF_OK) { - return ret; - } + if (gif->info.frame_count == 1) { + delay = NSGIF_INFINITE; + } else if (gif->info.loop_max != 0) { + uint32_t frame_next = frame; - if (frame_next < frame) { - if (nsgif__animation_complete( - gif->loop_count + 1, - gif->info.loop_max)) { - delay = NSGIF_INFINITE; + ret = nsgif__next_displayable_frame(gif, + &frame_next, NULL); + if (ret != NSGIF_OK) { + return ret; + } + + if (gif->data_complete && frame_next < frame) { + if (nsgif__animation_complete( + gif->loop_count + 1, + gif->info.loop_max)) { + delay = NSGIF_INFINITE; + } } } } @@ -1986,6 +2044,7 @@ const char *nsgif_strerror(nsgif_error err) [NSGIF_ERR_DATA_FRAME] = "Invalid frame data", [NSGIF_ERR_FRAME_COUNT] = "Excessive number of frames", [NSGIF_ERR_END_OF_DATA] = "Unexpected end of GIF source data", + [NSGIF_ERR_DATA_COMPLETE] = "Can't add data to completed GIF", [NSGIF_ERR_FRAME_DISPLAY] = "Frame can't be displayed", [NSGIF_ERR_ANIMATION_END] = "Animation complete", }; -- cgit v1.2.1