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
> 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

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
will also patch libnsgif.c to prevent it modifying the input.
Last updated 8 May 2022.
Last updated 4 Nov 2022.
# To do

View File

@ -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",
};

View File

@ -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.

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)
{
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;
}

View File

@ -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

View File

@ -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 );