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
|
> 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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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",
|
||||||
};
|
};
|
||||||
|
@ -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.
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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 );
|
||||||
|
Loading…
Reference in New Issue
Block a user