diff --git a/libvips/foreign/libnsgif/README-ns.md b/libvips/foreign/libnsgif/README-ns.md index e6219aab..5007da9b 100644 --- a/libvips/foreign/libnsgif/README-ns.md +++ b/libvips/foreign/libnsgif/README-ns.md @@ -67,6 +67,10 @@ it has reached the end of the source data. > buffer. Just be sure to call `nsgif_data_scan()` again with the new pointer > before making any other calls against that nsgif object. +When all the source data has been provided to `nsgif_data_scan()` it is +advisable to call `nsgif_data_complete()` (see below), although this is not +necessary to start decoding frames. + To decode the frames, you can call `nsgif_get_info()` to get the frame_count, and then call `nsgif_frame_decode()` for each frame, and manage the animation, and non-displayable frames yourself, or you can use the helper function, @@ -122,6 +126,20 @@ on-demand with: Note that this will be a no-op if the requested frame already happens to be the decoded frame. +You can call `nsgif_frame_prepare()` and `nsgif_frame_decode()` before all +of the GIF data has been provided using `nsgif_data_scan()` calls. For example +if you want to make a start decoding and displaying the early frames of the GIF +before the entire animation file has been downloaded. + +When you do this, `nsgif_frame_prepare()` will not loop the animation back to +the start unless you call `nsgif_data_complete()` to indicate all of the data +has been fetched. Calling `nsgif_data_complete()` also lets libnsgif display +any trailing truncated frame. + +```c + nsgif_data_complete(gif); +``` + Once you are done with the GIF, free up the nsgif object with: ```c diff --git a/libvips/foreign/libnsgif/README.md b/libvips/foreign/libnsgif/README.md index d1b49e6c..3cc64ec3 100644 --- a/libvips/foreign/libnsgif/README.md +++ b/libvips/foreign/libnsgif/README.md @@ -8,7 +8,7 @@ but within the libvips build system. Run `./update.sh` to update this copy of libnsgif from the upstream repo. It will also patch libnsgif.c to prevent it modifying the input. -Last updated 8 May 2022. +Last updated 4 Nov 2022. # To do diff --git a/libvips/foreign/libnsgif/gif.c b/libvips/foreign/libnsgif/gif.c index d2b6054e..2dc631ee 100644 --- a/libvips/foreign/libnsgif/gif.c +++ b/libvips/foreign/libnsgif/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) @@ -1757,7 +1806,7 @@ static void nsgif__redraw_rect_extend( } static uint32_t nsgif__frame_next( - nsgif_t *gif, + const nsgif_t *gif, bool partial, uint32_t frame) { @@ -1774,7 +1823,7 @@ static uint32_t nsgif__frame_next( } static nsgif_error nsgif__next_displayable_frame( - nsgif_t *gif, + const nsgif_t *gif, uint32_t *frame, uint32_t *delay) { @@ -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", }; diff --git a/libvips/foreign/libnsgif/nsgif.h b/libvips/foreign/libnsgif/nsgif.h index 208bc27c..7e6aa83b 100644 --- a/libvips/foreign/libnsgif/nsgif.h +++ b/libvips/foreign/libnsgif/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/libvips/foreign/libnsgif/test/nsgif.c b/libvips/foreign/libnsgif/test/nsgif.c index 4a9e31c5..7d61504f 100644 --- a/libvips/foreign/libnsgif/test/nsgif.c +++ b/libvips/foreign/libnsgif/test/nsgif.c @@ -137,8 +137,7 @@ static uint8_t *load_file(const char *path, size_t *data_size) static void warning(const char *context, nsgif_error err) { - fprintf(stderr, "%s failed: %s\n", - context, nsgif_strerror(err)); + fprintf(stderr, "%s: %s\n", context, nsgif_strerror(err)); } static void print_gif_info(const nsgif_info_t *info) @@ -385,6 +384,8 @@ int main(int argc, char *argv[]) warning("nsgif_data_scan", err); } + nsgif_data_complete(gif); + if (nsgif_options.loops == 0) { nsgif_options.loops = 1; } diff --git a/libvips/foreign/libnsgif/update.sh b/libvips/foreign/libnsgif/update.sh index 99828a9c..b8fb9250 100755 --- a/libvips/foreign/libnsgif/update.sh +++ b/libvips/foreign/libnsgif/update.sh @@ -21,6 +21,7 @@ cp libnsgif/test/nsgif.c test/ if [ -d patches ]; then echo applying patches ... for patch in patches/*.patch; do + [ -f "$patch" ] || continue patch -p0 <$patch done fi diff --git a/libvips/foreign/nsgifload.c b/libvips/foreign/nsgifload.c index 34da88a6..534b2502 100644 --- a/libvips/foreign/nsgifload.c +++ b/libvips/foreign/nsgifload.c @@ -361,6 +361,7 @@ vips_foreign_load_nsgif_header( VipsForeignLoad *load ) result = nsgif_data_scan( gif->anim, size, (void *) data ); VIPS_DEBUG_MSG( "nsgif_data_scan() = %s\n", nsgif_strerror( result ) ); + nsgif_data_complete( gif->anim ); gif->info = nsgif_get_info(gif->anim); #ifdef VERBOSE print_animation( gif->anim, gif->info );