Update to latest upstream libnsgif and call nsgif_data_complete (#3141)

* libnsgif: update script: Fix to handle dir with no patches

* libnsgif: Update to latest upstream

Fixes loading of broken gifs with truncated final frame.

* nsgifload: Call nsgif_data_complete after data scan

This allows libnsgif to distinguish between awaiting more
data, and a broken truncated GIF. In the latter case we
can display what we have.
This commit is contained in:
Michael Drake 2022-11-05 04:14:45 +00:00 committed by GitHub
parent 3d29daf553
commit 2189e49dc7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 133 additions and 24 deletions

View File

@ -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 > buffer. Just be sure to call `nsgif_data_scan()` again with the new pointer
> before making any other calls against that nsgif object. > 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, 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 then call `nsgif_frame_decode()` for each frame, and manage the animation,
and non-displayable frames yourself, or you can use the helper function, 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 Note that this will be a no-op if the requested frame already happens to be
the decoded frame. 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: Once you are done with the GIF, free up the nsgif object with:
```c ```c

View File

@ -8,7 +8,7 @@ but within the libvips build system.
Run `./update.sh` to update this copy of libnsgif from the upstream repo. It 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. will also patch libnsgif.c to prevent it modifying the input.
Last updated 8 May 2022. Last updated 4 Nov 2022.
# To do # To do

View File

@ -40,6 +40,9 @@ typedef struct nsgif_frame {
/** whether a full image redraw is required */ /** whether a full image redraw is required */
bool redraw_required; bool redraw_required;
/** Amount of LZW data found in scan */
uint32_t lzw_data_length;
/** the index designating a transparent pixel */ /** the index designating a transparent pixel */
uint32_t transparency_index; uint32_t transparency_index;
@ -90,6 +93,12 @@ struct nsgif {
/** number of frames partially decoded */ /** number of frames partially decoded */
uint32_t frame_count_partial; 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 */ /** pointer to GIF data */
const uint8_t *buf; const uint8_t *buf;
/** current index into GIF data */ /** current index into GIF data */
@ -613,6 +622,11 @@ static inline nsgif_error nsgif__decode(
frame_data, colour_table); 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; return ret;
} }
@ -1213,16 +1227,19 @@ static nsgif_error nsgif__parse_image_data(
len--; len--;
while (block_size != 1) { 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; 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) { if (block_size > len) {
block_size = len; frame->lzw_data_length += len;
return NSGIF_OK; return NSGIF_OK;
} }
len -= block_size; len -= block_size;
data += block_size; data += block_size;
frame->lzw_data_length += block_size;
} }
*pos = data; *pos = data;
@ -1258,14 +1275,16 @@ static struct nsgif_frame *nsgif__get_frame(
frame = &gif->frames[frame_idx]; frame = &gif->frames[frame_idx];
frame->transparency_index = NSGIF_NO_TRANSPARENCY;
frame->frame_offset = gif->buf_pos;
frame->info.local_palette = false; frame->info.local_palette = false;
frame->info.transparency = false; frame->info.transparency = false;
frame->redraw_required = false;
frame->info.display = false; frame->info.display = false;
frame->info.disposal = 0; frame->info.disposal = 0;
frame->info.delay = 10; 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; frame->decoded = false;
} }
@ -1593,6 +1612,10 @@ nsgif_error nsgif_data_scan(
nsgif_error ret; nsgif_error ret;
uint32_t frames; uint32_t frames;
if (gif->data_complete) {
return NSGIF_ERR_DATA_COMPLETE;
}
/* Initialize values */ /* Initialize values */
gif->buf_len = size; gif->buf_len = size;
gif->buf = data; gif->buf = data;
@ -1734,6 +1757,32 @@ nsgif_error nsgif_data_scan(
return ret; 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( static void nsgif__redraw_rect_extend(
const nsgif_rect_t *frame, const nsgif_rect_t *frame,
nsgif_rect_t *redraw) nsgif_rect_t *redraw)
@ -1757,7 +1806,7 @@ static void nsgif__redraw_rect_extend(
} }
static uint32_t nsgif__frame_next( static uint32_t nsgif__frame_next(
nsgif_t *gif, const nsgif_t *gif,
bool partial, bool partial,
uint32_t frame) uint32_t frame)
{ {
@ -1774,7 +1823,7 @@ static uint32_t nsgif__frame_next(
} }
static nsgif_error nsgif__next_displayable_frame( static nsgif_error nsgif__next_displayable_frame(
nsgif_t *gif, const nsgif_t *gif,
uint32_t *frame, uint32_t *frame,
uint32_t *delay) uint32_t *delay)
{ {
@ -1782,7 +1831,11 @@ static nsgif_error nsgif__next_displayable_frame(
do { do {
next = nsgif__frame_next(gif, false, next); 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; return NSGIF_ERR_FRAME_DISPLAY;
} }
@ -1850,21 +1903,26 @@ nsgif_error nsgif_frame_prepare(
gif->loop_count++; gif->loop_count++;
} }
if (gif->info.frame_count == 1) { if (gif->data_complete) {
delay = NSGIF_INFINITE; /* Check for last frame, which has infinite delay. */
} else if (gif->info.loop_max != 0) { if (gif->info.frame_count == 1) {
uint32_t frame_next = frame; delay = NSGIF_INFINITE;
ret = nsgif__next_displayable_frame(gif, &frame_next, NULL); } else if (gif->info.loop_max != 0) {
if (ret != NSGIF_OK) { uint32_t frame_next = frame;
return ret;
}
if (frame_next < frame) { ret = nsgif__next_displayable_frame(gif,
if (nsgif__animation_complete( &frame_next, NULL);
gif->loop_count + 1, if (ret != NSGIF_OK) {
gif->info.loop_max)) { return ret;
delay = NSGIF_INFINITE; }
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_DATA_FRAME] = "Invalid frame data",
[NSGIF_ERR_FRAME_COUNT] = "Excessive number of frames", [NSGIF_ERR_FRAME_COUNT] = "Excessive number of frames",
[NSGIF_ERR_END_OF_DATA] = "Unexpected end of GIF source data", [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_FRAME_DISPLAY] = "Frame can't be displayed",
[NSGIF_ERR_ANIMATION_END] = "Animation complete", [NSGIF_ERR_ANIMATION_END] = "Animation complete",
}; };

View File

@ -89,6 +89,11 @@ typedef enum {
*/ */
NSGIF_ERR_END_OF_DATA, 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. * 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 * 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. * 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 * 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 * 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 * 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, size_t size,
const uint8_t *data); 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. * 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 * returned `delay_cs` will be \ref NSGIF_INFINITE, indicating that the frame
* should be shown forever. * 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[in] gif The \ref nsgif_t object.
* \param[out] area The area in pixels that must be redrawn. * \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] delay_cs Time to wait after frame_new before next frame in cs.

View File

@ -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) static void warning(const char *context, nsgif_error err)
{ {
fprintf(stderr, "%s failed: %s\n", fprintf(stderr, "%s: %s\n", context, nsgif_strerror(err));
context, nsgif_strerror(err));
} }
static void print_gif_info(const nsgif_info_t *info) 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); warning("nsgif_data_scan", err);
} }
nsgif_data_complete(gif);
if (nsgif_options.loops == 0) { if (nsgif_options.loops == 0) {
nsgif_options.loops = 1; nsgif_options.loops = 1;
} }

View File

@ -21,6 +21,7 @@ cp libnsgif/test/nsgif.c test/
if [ -d patches ]; then if [ -d patches ]; then
echo applying patches ... echo applying patches ...
for patch in patches/*.patch; do for patch in patches/*.patch; do
[ -f "$patch" ] || continue
patch -p0 <$patch patch -p0 <$patch
done done
fi fi

View File

@ -361,6 +361,7 @@ vips_foreign_load_nsgif_header( VipsForeignLoad *load )
result = nsgif_data_scan( gif->anim, size, (void *) data ); result = nsgif_data_scan( gif->anim, size, (void *) data );
VIPS_DEBUG_MSG( "nsgif_data_scan() = %s\n", nsgif_strerror( result ) ); VIPS_DEBUG_MSG( "nsgif_data_scan() = %s\n", nsgif_strerror( result ) );
nsgif_data_complete( gif->anim );
gif->info = nsgif_get_info(gif->anim); gif->info = nsgif_get_info(gif->anim);
#ifdef VERBOSE #ifdef VERBOSE
print_animation( gif->anim, gif->info ); print_animation( gif->anim, gif->info );