Update to latest libnsgif (#2747)

* libnsgif: Update to latest upstream.

* nsgifload: Update for new pixel format API.

LibNSGIF now lets the client select a pixel colour component
order. We use NSGIF_BITMAP_FMT_R8G8B8A8, which is the same
format LibNSGIF always decoded to.

* nsgifload: Remove loop_count from VERBOSE output.

This was internal state for a nsgif_frame_prepare() managed
animation, but VIPS doesn't use nsgif_frame_prepare(). The
loop_count member has been removed from the public structure.
This commit is contained in:
Michael Drake 2022-04-04 13:57:20 +01:00 committed by GitHub
parent 89bd46d1c4
commit 0f30690360
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 273 additions and 81 deletions

View File

@ -22,14 +22,17 @@ Using
LibNSGIF allows the client to allocate the bitmap into which the GIF is
decoded. The client can have an arbitrary bitmap structure, that is simply
a void pointer to LibNSGIF. The client must provide a callback table for
interacting with bitmaps. This table must include as a minimum functions to
create and destroy bitmaps, and a function to get a pointer to the bitmap's
pixel data buffer.
interacting with bitmaps, and the required client bitmap pixel format.
The bitmap table must include as a minimum functions to create and destroy
bitmaps, and a function to get a pointer to the bitmap's pixel data buffer.
LibNSGIF always decodes to a 32bpp, 8 bits per channel bitmap pixel format,
however it allows the client to control the colour component ordering.
To load a GIF, first create an nsgif object with `nsgif_create()`.
```c
err = nsgif_create(&bitmap_callbacks, &gif);
err = nsgif_create(&bitmap_callbacks, NSGIF_BITMAP_FMT_R8G8B8A8, &gif);
if (err != NSGIF_OK) {
fprintf(stderr, "%s\n", nsgif_strerror(err));
// Handle error

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 Mar 2022.
Last updated 4 Apr 2022.
# To do

View File

@ -20,6 +20,16 @@
/** Maximum colour table size */
#define NSGIF_MAX_COLOURS 256
/** Default minimum allowable frame delay in cs. */
#define NSGIF_FRAME_DELAY_MIN 2
/**
* Default frame delay to apply.
*
* Used when a frame delay lower than the minimum is requested.
*/
#define NSGIF_FRAME_DELAY_DEFAULT 10
/** GIF frame data */
typedef struct nsgif_frame {
struct nsgif_frame_info info;
@ -40,6 +50,14 @@ typedef struct nsgif_frame {
uint32_t flags;
} nsgif_frame;
/** Pixel format: colour component order. */
struct nsgif_colour_layout {
uint8_t r; /**< Byte offset within pixel to red component. */
uint8_t g; /**< Byte offset within pixel to green component. */
uint8_t b; /**< Byte offset within pixel to blue component. */
uint8_t a; /**< Byte offset within pixel to alpha component. */
};
/** GIF animation data */
struct nsgif {
struct nsgif_info info;
@ -57,9 +75,15 @@ struct nsgif {
/** currently decoded image; stored as bitmap from bitmap_create callback */
nsgif_bitmap_t *frame_image;
/** Minimum allowable frame delay. */
uint16_t delay_min;
/** Frame delay to apply when delay is less than \ref delay_min. */
uint16_t delay_default;
/** number of animation loops so far */
int loop_count;
/** number of frames partially decoded */
uint32_t frame_count_partial;
@ -83,6 +107,8 @@ struct nsgif {
bool global_colours;
/** current colour table */
uint32_t *colour_table;
/** Client's colour component order. */
struct nsgif_colour_layout colour_layout;
/** global colour table */
uint32_t global_colour_table[NSGIF_MAX_COLOURS];
/** local colour table */
@ -580,30 +606,6 @@ static inline nsgif_error nsgif__decode(
return ret;
}
/**
* Helper to assign a pixel representation from a gif background colour array.
*
* \param[in] bg The background colour to read from.
* \param[out] px The pixel colour to write.
*/
static inline void nsgif__gif_bg_to_px(
const uint8_t bg[4], uint32_t *px)
{
*px = *(uint32_t *)bg;
}
/**
* Helper to assign a gif background colour array from a pixel representation.
*
* \param[in] px The pixel colour to read from.
* \param[out] bg The background colour to write.
*/
static inline void nsgif__gif_px_to_bg(
const uint32_t *px, uint8_t bg[4])
{
*(uint32_t *)bg = *px;
}
/**
* Restore a GIF to the background colour.
*
@ -646,9 +648,7 @@ static void nsgif__restore_bg(
uint32_t *scanline = bitmap + offset_x +
(offset_y + y) * gif->info.width;
for (uint32_t x = 0; x < width; x++) {
nsgif__gif_bg_to_px(
gif->info.background,
&scanline[x]);
scanline[x] = gif->info.background;
}
}
}
@ -1042,6 +1042,7 @@ static nsgif_error nsgif__parse_image_descriptor(
static nsgif_error nsgif__colour_table_extract(
struct nsgif *gif,
uint32_t colour_table[NSGIF_MAX_COLOURS],
const struct nsgif_colour_layout *layout,
size_t colour_table_entries,
const uint8_t **pos,
bool decode)
@ -1060,16 +1061,16 @@ static nsgif_error nsgif__colour_table_extract(
while (count--) {
/* Gif colour map contents are r,g,b.
*
* We want to pack them bytewise into the
* colour table, such that the red component
* is in byte 0 and the alpha component is in
* byte 3.
* We want to pack them bytewise into the colour table,
* according to the client colour layout.
*/
*entry++ = *data++; /* r */
*entry++ = *data++; /* g */
*entry++ = *data++; /* b */
*entry++ = 0xff; /* a */
entry[layout->r] = *data++;
entry[layout->g] = *data++;
entry[layout->b] = *data++;
entry[layout->a] = 0xff;
entry += sizeof(uint32_t);
}
}
@ -1104,7 +1105,8 @@ static nsgif_error nsgif__parse_colour_table(
return NSGIF_OK;
}
ret = nsgif__colour_table_extract(gif, gif->local_colour_table,
ret = nsgif__colour_table_extract(gif,
gif->local_colour_table, &gif->colour_layout,
2 << (frame->flags & NSGIF_COLOUR_TABLE_SIZE_MASK),
pos, decode);
if (ret != NSGIF_OK) {
@ -1343,8 +1345,90 @@ void nsgif_destroy(nsgif_t *gif)
free(gif);
}
/**
* Check whether the host is little endian.
*
* Checks whether least significant bit is in the first byte of a `uint16_t`.
*
* \return true if host is little endian.
*/
static inline bool nsgif__host_is_little_endian(void)
{
const uint16_t test = 1;
return ((const uint8_t *) &test)[0];
}
static struct nsgif_colour_layout nsgif__bitmap_fmt_to_colour_layout(
nsgif_bitmap_fmt_t bitmap_fmt)
{
bool le = nsgif__host_is_little_endian();
/* Map endian-dependant formats to byte-wise format for the host. */
switch (bitmap_fmt) {
case NSGIF_BITMAP_FMT_RGBA8888:
bitmap_fmt = (le) ? NSGIF_BITMAP_FMT_A8B8G8R8
: NSGIF_BITMAP_FMT_R8G8B8A8;
break;
case NSGIF_BITMAP_FMT_BGRA8888:
bitmap_fmt = (le) ? NSGIF_BITMAP_FMT_A8R8G8B8
: NSGIF_BITMAP_FMT_B8G8R8A8;
break;
case NSGIF_BITMAP_FMT_ARGB8888:
bitmap_fmt = (le) ? NSGIF_BITMAP_FMT_B8G8R8A8
: NSGIF_BITMAP_FMT_A8R8G8B8;
break;
case NSGIF_BITMAP_FMT_ABGR8888:
bitmap_fmt = (le) ? NSGIF_BITMAP_FMT_R8G8B8A8
: NSGIF_BITMAP_FMT_A8B8G8R8;
break;
default:
break;
}
/* Set up colour component order for bitmap format. */
switch (bitmap_fmt) {
default:
/* Fall through. */
case NSGIF_BITMAP_FMT_R8G8B8A8:
return (struct nsgif_colour_layout) {
.r = 0,
.g = 1,
.b = 2,
.a = 3,
};
case NSGIF_BITMAP_FMT_B8G8R8A8:
return (struct nsgif_colour_layout) {
.b = 0,
.g = 1,
.r = 2,
.a = 3,
};
case NSGIF_BITMAP_FMT_A8R8G8B8:
return (struct nsgif_colour_layout) {
.a = 0,
.r = 1,
.g = 2,
.b = 3,
};
case NSGIF_BITMAP_FMT_A8B8G8R8:
return (struct nsgif_colour_layout) {
.a = 0,
.b = 1,
.g = 2,
.r = 3,
};
}
}
/* exported function documented in nsgif.h */
nsgif_error nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif_t **gif_out)
nsgif_error nsgif_create(
const nsgif_bitmap_cb_vt *bitmap_vt,
nsgif_bitmap_fmt_t bitmap_fmt,
nsgif_t **gif_out)
{
nsgif_t *gif;
@ -1357,13 +1441,25 @@ nsgif_error nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif_t **gif_out)
gif->decoded_frame = NSGIF_FRAME_INVALID;
gif->prev_index = NSGIF_FRAME_INVALID;
gif->delay_min = 2;
gif->delay_default = 10;
gif->delay_min = NSGIF_FRAME_DELAY_MIN;
gif->delay_default = NSGIF_FRAME_DELAY_DEFAULT;
gif->colour_layout = nsgif__bitmap_fmt_to_colour_layout(bitmap_fmt);
*gif_out = gif;
return NSGIF_OK;
}
/* exported function documented in nsgif.h */
void nsgif_set_frame_delay_behaviour(
nsgif_t *gif,
uint16_t delay_min,
uint16_t delay_default)
{
gif->delay_min = delay_min;
gif->delay_default = delay_default;
}
/**
* Read GIF header.
*
@ -1535,6 +1631,7 @@ nsgif_error nsgif_data_scan(
if (gif->global_colours) {
ret = nsgif__colour_table_extract(gif,
gif->global_colour_table,
&gif->colour_layout,
gif->colour_table_size,
&nsgif_data, true);
if (ret != NSGIF_OK) {
@ -1544,27 +1641,30 @@ nsgif_error nsgif_data_scan(
gif->buf_pos = (nsgif_data - gif->buf);
} else {
/* Create a default colour table with the first two
* colours as black and white
*/
uint32_t *entry = gif->global_colour_table;
* colours as black and white. */
uint8_t *entry = (uint8_t *)gif->global_colour_table;
entry[0] = 0x00000000;
/* Force Alpha channel to opaque */
((uint8_t *) entry)[3] = 0xff;
/* Black */
entry[gif->colour_layout.r] = 0x00;
entry[gif->colour_layout.g] = 0x00;
entry[gif->colour_layout.b] = 0x00;
entry[gif->colour_layout.a] = 0xFF;
entry[1] = 0xffffffff;
entry += sizeof(uint32_t);
/* White */
entry[gif->colour_layout.r] = 0xFF;
entry[gif->colour_layout.g] = 0xFF;
entry[gif->colour_layout.b] = 0xFF;
entry[gif->colour_layout.a] = 0xFF;
}
if (gif->global_colours &&
gif->bg_index < gif->colour_table_size) {
size_t bg_idx = gif->bg_index;
nsgif__gif_px_to_bg(
&gif->global_colour_table[bg_idx],
gif->info.background);
gif->info.background = gif->global_colour_table[bg_idx];
} else {
nsgif__gif_px_to_bg(
&gif->global_colour_table[0],
gif->info.background);
gif->info.background = gif->global_colour_table[0];
}
}
@ -1663,7 +1763,7 @@ static inline bool nsgif__animation_complete(int count, int max)
nsgif_error nsgif_reset(
nsgif_t *gif)
{
gif->info.loop_count = 0;
gif->loop_count = 0;
gif->frame = NSGIF_FRAME_INVALID;
return NSGIF_OK;
@ -1691,7 +1791,7 @@ nsgif_error nsgif_frame_prepare(
}
if (nsgif__animation_complete(
gif->info.loop_count,
gif->loop_count,
gif->info.loop_max)) {
return NSGIF_ERR_ANIMATION_END;
}
@ -1702,7 +1802,7 @@ nsgif_error nsgif_frame_prepare(
}
if (gif->frame != NSGIF_FRAME_INVALID && frame < gif->frame) {
gif->info.loop_count++;
gif->loop_count++;
}
if (gif->info.frame_count == 1) {
@ -1717,7 +1817,7 @@ nsgif_error nsgif_frame_prepare(
if (frame_next < frame) {
if (nsgif__animation_complete(
gif->info.loop_count + 1,
gif->loop_count + 1,
gif->info.loop_max)) {
delay = NSGIF_INFINITE;
}

View File

@ -98,15 +98,70 @@ typedef enum {
NSGIF_ERR_ANIMATION_END,
} nsgif_error;
/**
* NSGIF \ref nsgif_bitmap_t pixel format.
*
* All pixel formats are 32 bits per pixel (bpp). The different formats
* allow control over the ordering of the colour channels. All colour
* channels are 8 bits wide.
*
* Note that the GIF file format only supports an on/off mask, so the
* alpha (A) component (opacity) will always have a value of `0` (fully
* transparent) or `255` (fully opaque).
*/
typedef enum nsgif_bitmap_fmt {
/** Bite-wise RGBA: Byte order: 0xRR, 0xGG, 0xBB, 0xAA. */
NSGIF_BITMAP_FMT_R8G8B8A8,
/** Bite-wise BGRA: Byte order: 0xBB, 0xGG, 0xRR, 0xAA. */
NSGIF_BITMAP_FMT_B8G8R8A8,
/** Bite-wise ARGB: Byte order: 0xAA, 0xRR, 0xGG, 0xBB. */
NSGIF_BITMAP_FMT_A8R8G8B8,
/** Bite-wise ABGR: Byte order: 0xAA, 0xBB, 0xGG, 0xRR. */
NSGIF_BITMAP_FMT_A8B8G8R8,
/**
* 32-bit RGBA (0xRRGGBBAA).
*
* * On little endian host, same as \ref NSGIF_BITMAP_FMT_A8B8G8R8.
* * On big endian host, same as \ref NSGIF_BITMAP_FMT_R8G8B8A8.
*/
NSGIF_BITMAP_FMT_RGBA8888,
/**
* 32-bit BGRA (0xBBGGRRAA).
*
* * On little endian host, same as \ref NSGIF_BITMAP_FMT_A8R8G8B8.
* * On big endian host, same as \ref NSGIF_BITMAP_FMT_B8G8R8A8.
*/
NSGIF_BITMAP_FMT_BGRA8888,
/**
* 32-bit ARGB (0xAARRGGBB).
*
* * On little endian host, same as \ref NSGIF_BITMAP_FMT_B8G8R8A8.
* * On big endian host, same as \ref NSGIF_BITMAP_FMT_A8R8G8B8.
*/
NSGIF_BITMAP_FMT_ARGB8888,
/**
* 32-bit BGRA (0xAABBGGRR).
*
* * On little endian host, same as \ref NSGIF_BITMAP_FMT_R8G8B8A8.
* * On big endian host, same as \ref NSGIF_BITMAP_FMT_A8B8G8R8.
*/
NSGIF_BITMAP_FMT_ABGR8888,
} nsgif_bitmap_fmt_t;
/**
* Client bitmap type.
*
* These are client-created and destroyed, via the \ref bitmap callbacks,
* but they are owned by a \ref nsgif_t.
*
* The pixel buffer is is 32bpp, treated as individual bytes in the component
* order RR GG BB AA. For example, a 1x1 image with a single orange pixel would
* be encoded as the following sequence of bytes: 0xff, 0x88, 0x00, 0x00.
* See \ref nsgif_bitmap_fmt for pixel format information.
*/
typedef void nsgif_bitmap_t;
@ -176,13 +231,15 @@ const char *nsgif_strerror(nsgif_error err);
/**
* Create the NSGIF object.
*
* \param[in] bitmap_vt Bitmap operation functions v-table.
* \param[out] gif_out Return \ref nsgif_t object on success.
* \param[in] bitmap_vt Bitmap operation functions v-table.
* \param[in] bitmap_fmt Bitmap pixel format specification.
* \param[out] gif_out Return \ref nsgif_t object on success.
*
* \return NSGIF_OK on success, or appropriate error otherwise.
*/
nsgif_error nsgif_create(
const nsgif_bitmap_cb_vt *bitmap_vt,
nsgif_bitmap_fmt_t bitmap_fmt,
nsgif_t **gif_out);
/**
@ -286,10 +343,8 @@ typedef struct nsgif_info {
uint32_t frame_count;
/** number of times to play animation (zero means loop forever) */
int loop_max;
/** number of animation loops so far */
int loop_count;
/** background colour in same pixel format as \ref nsgif_bitmap_t. */
uint8_t background[4];
uint32_t background;
} nsgif_info_t;
/**
@ -353,4 +408,35 @@ const nsgif_frame_info_t *nsgif_get_frame_info(
const nsgif_t *gif,
uint32_t frame);
/**
* Configure handling of small frame delays.
*
* Historically people created GIFs with a tiny frame delay, however the slow
* hardware of the time meant they actually played much slower. As computers
* sped up, to prevent animations playing faster than intended, decoders came
* to ignore overly small frame delays.
*
* By default a \ref nsgif_frame_prepare() managed animation will override
* frame delays of less than 2 centiseconds with a default frame delay of
* 10 centiseconds. This matches the behaviour of web browsers and other
* renderers.
*
* Both the minimum and the default values can be overridden for a given GIF
* by the client. To get frame delays exactly as specified by the GIF file, set
* \ref delay_min to zero.
*
* Note that this does not affect the frame delay in the frame info
* (\ref nsgif_frame_info_t) structure, which will always contain values
* specified by the GIF.
*
* \param[in] gif The \ref nsgif_t object to configure.
* \param[in] delay_min The minimum frame delay in centiseconds.
* \param[in] delay_default The delay to use if a frame delay is less than
* \ref delay_min.
*/
void nsgif_set_frame_delay_behaviour(
nsgif_t *gif,
uint16_t delay_min,
uint16_t delay_default);
#endif

View File

@ -135,15 +135,17 @@ static void warning(const char *context, nsgif_error err)
static void print_gif_info(const nsgif_info_t *info)
{
const uint8_t *bg = (uint8_t *) &info->background;
fprintf(stdout, "gif:\n");
fprintf(stdout, " width: %"PRIu32"\n", info->width);
fprintf(stdout, " height: %"PRIu32"\n", info->height);
fprintf(stdout, " max-loops: %"PRIu32"\n", info->loop_max);
fprintf(stdout, " frame-count: %"PRIu32"\n", info->frame_count);
fprintf(stdout, " background:\n");
fprintf(stdout, " red: 0x%"PRIx8"\n", info->background[0]);
fprintf(stdout, " green: 0x%"PRIx8"\n", info->background[1]);
fprintf(stdout, " blue: 0x%"PRIx8"\n", info->background[2]);
fprintf(stdout, " red: 0x%"PRIx8"\n", bg[0]);
fprintf(stdout, " green: 0x%"PRIx8"\n", bg[1]);
fprintf(stdout, " blue: 0x%"PRIx8"\n", bg[2]);
fprintf(stdout, " frames:\n");
}
@ -272,7 +274,7 @@ int main(int argc, char *argv[])
}
/* create our gif animation */
err = nsgif_create(&bitmap_callbacks, &gif);
err = nsgif_create(&bitmap_callbacks, NSGIF_BITMAP_FMT_R8G8B8A8, &gif);
if (err != NSGIF_OK) {
warning("nsgif_create", err);
return EXIT_FAILURE;

View File

@ -216,18 +216,15 @@ static void
print_animation( nsgif_t *anim, const nsgif_info_t *info )
{
int i;
const uint8_t *bg = (uint8_t *) &info->background;
printf( "animation:\n" );
printf( " width = %d\n", info->width );
printf( " height = %d\n", info->height );
printf( " frame_count = %d\n", info->frame_count );
printf( " loop_max = %d\n", info->loop_max );
printf( " loop_count = %d\n", info->loop_count );
printf( " background = %d %d %d %d\n",
info->background[0],
info->background[1],
info->background[2],
info->background[3] );
bg[0], bg[1], bg[2], bg[3] );
for( i = 0; i < info->frame_count; i++ ) {
printf( "%d ", i );
@ -242,6 +239,7 @@ vips_foreign_load_nsgif_set_header( VipsForeignLoadNsgif *gif,
VipsImage *image )
{
double array[3];
const uint8_t *bg;
VIPS_DEBUG_MSG( "vips_foreign_load_nsgif_set_header:\n" );
@ -265,9 +263,11 @@ vips_foreign_load_nsgif_set_header( VipsForeignLoadNsgif *gif,
vips_image_set_array_int( image, "delay",
gif->delay, gif->info->frame_count );
array[0] = gif->info->background[0];
array[1] = gif->info->background[1];
array[2] = gif->info->background[2];
bg = (uint8_t *) &gif->info->background;
array[0] = bg[0];
array[1] = bg[1];
array[2] = bg[2];
vips_image_set_array_double( image, "background", array, 3 );
@ -557,6 +557,7 @@ vips_foreign_load_nsgif_init( VipsForeignLoadNsgif *gif )
{
nsgif_error result = nsgif_create(
&vips_foreign_load_nsgif_bitmap_callbacks,
NSGIF_BITMAP_FMT_R8G8B8A8,
&gif->anim );
if (result != NSGIF_OK) {
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif );