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:
parent
3d29daf553
commit
2189e49dc7
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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",
|
||||
};
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 );
|
||||
|
Loading…
Reference in New Issue
Block a user