Update LibNSGIF to latest upstream (#2791)

* libnsgif: Update to latest upstream

* nsgifload: Add palette info to debug output.
This commit is contained in:
Michael Drake 2022-05-09 10:20:57 +01:00 committed by GitHub
parent 460a19b78e
commit ceaa2d2096
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 287 additions and 52 deletions

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 15 Apr 2022.
Last updated 8 May 2022.
# To do

View File

@ -17,9 +17,6 @@
#include "lzw.h"
#include "nsgif.h"
/** Maximum colour table size */
#define NSGIF_MAX_COLOURS 256
/** Default minimum allowable frame delay in cs. */
#define NSGIF_FRAME_DELAY_MIN 2
@ -35,7 +32,7 @@ typedef struct nsgif_frame {
struct nsgif_frame_info info;
/** offset (in bytes) to the GIF frame data */
uint32_t frame_pointer;
uint32_t frame_offset;
/** whether the frame has previously been decoded. */
bool decoded;
/** whether the frame is totally opaque */
@ -46,6 +43,9 @@ typedef struct nsgif_frame {
/** the index designating a transparent pixel */
uint32_t transparency_index;
/** offset to frame colour table */
uint32_t colour_table_offset;
/* Frame flags */
uint32_t flags;
} nsgif_frame;
@ -72,8 +72,11 @@ struct nsgif {
uint32_t frame;
/** current frame decoded to bitmap */
uint32_t decoded_frame;
/** currently decoded image; stored as bitmap from bitmap_create callback */
nsgif_bitmap_t *frame_image;
/** Row span of frame_image in pixels. */
uint32_t rowspan;
/** Minimum allowable frame delay. */
uint16_t delay_min;
@ -100,11 +103,9 @@ struct nsgif {
uint32_t bg_index;
/** image aspect ratio (ignored) */
uint32_t aspect_ratio;
/** size of colour table (in entries) */
/** size of global colour table (in entries) */
uint32_t colour_table_size;
/** whether the GIF has a global colour table */
bool global_colours;
/** current colour table */
uint32_t *colour_table;
/** Client's colour component order. */
@ -223,6 +224,11 @@ static inline uint32_t* nsgif__bitmap_get(
return NULL;
}
gif->rowspan = gif->info.width;
if (gif->bitmap.get_rowspan) {
gif->rowspan = gif->bitmap.get_rowspan(gif->frame_image);
}
/* Get the frame data */
assert(gif->bitmap.get_buffer);
return (void *)gif->bitmap.get_buffer(gif->frame_image);
@ -463,7 +469,7 @@ static nsgif_error nsgif__decode_complex(
uint32_t *frame_scanline;
frame_scanline = frame_data + offset_x +
(y + offset_y) * gif->info.width;
(y + offset_y) * gif->rowspan;
x = width;
while (x > 0) {
@ -594,7 +600,9 @@ static inline nsgif_error nsgif__decode(
uint32_t transparency_index = frame->transparency_index;
uint32_t *restrict colour_table = gif->colour_table;
if (interlace == false && width == gif->info.width && offset_x == 0) {
if (interlace == false && offset_x == 0 &&
width == gif->info.width &&
width == gif->rowspan) {
ret = nsgif__decode_simple(gif, height, offset_y,
data, transparency_index,
frame_data, colour_table);
@ -1031,6 +1039,38 @@ static nsgif_error nsgif__parse_image_descriptor(
return NSGIF_OK;
}
/**
* Extract a GIF colour table into a LibNSGIF colour table buffer.
*
* \param[in] colour_table The colour table to populate.
* \param[in] layout la.
* \param[in] colour_table_entries The number of colour table entries.
* \param[in] Data Raw colour table data.
*/
static void nsgif__colour_table_decode(
uint32_t colour_table[NSGIF_MAX_COLOURS],
const struct nsgif_colour_layout *layout,
size_t colour_table_entries,
const uint8_t *data)
{
uint8_t *entry = (uint8_t *)colour_table;
while (colour_table_entries--) {
/* Gif colour map contents are r,g,b.
*
* We want to pack them bytewise into the colour table,
* according to the client colour layout.
*/
entry[layout->r] = *data++;
entry[layout->g] = *data++;
entry[layout->b] = *data++;
entry[layout->a] = 0xff;
entry += sizeof(uint32_t);
}
}
/**
* Extract a GIF colour table into a LibNSGIF colour table buffer.
*
@ -1041,42 +1081,25 @@ static nsgif_error nsgif__parse_image_descriptor(
* \param[in] decode Whether to decode the colour table.
* \return NSGIF_OK on success, appropriate error otherwise.
*/
static nsgif_error nsgif__colour_table_extract(
struct nsgif *gif,
static inline nsgif_error nsgif__colour_table_extract(
uint32_t colour_table[NSGIF_MAX_COLOURS],
const struct nsgif_colour_layout *layout,
size_t colour_table_entries,
const uint8_t **pos,
const uint8_t *data,
size_t data_len,
size_t *used,
bool decode)
{
const uint8_t *data = *pos;
size_t len = gif->buf + gif->buf_len - data;
if (len < colour_table_entries * 3) {
if (data_len < colour_table_entries * 3) {
return NSGIF_ERR_END_OF_DATA;
}
if (decode) {
int count = colour_table_entries;
uint8_t *entry = (uint8_t *)colour_table;
while (count--) {
/* Gif colour map contents are r,g,b.
*
* We want to pack them bytewise into the colour table,
* according to the client colour layout.
*/
entry[layout->r] = *data++;
entry[layout->g] = *data++;
entry[layout->b] = *data++;
entry[layout->a] = 0xff;
entry += sizeof(uint32_t);
}
nsgif__colour_table_decode(colour_table, layout,
colour_table_entries, data);
}
*pos += colour_table_entries * 3;
*used = colour_table_entries * 3;
return NSGIF_OK;
}
@ -1098,6 +1121,9 @@ static nsgif_error nsgif__parse_colour_table(
bool decode)
{
nsgif_error ret;
const uint8_t *data = *pos;
size_t len = gif->buf + gif->buf_len - data;
size_t used_bytes;
assert(gif != NULL);
assert(frame != NULL);
@ -1107,15 +1133,25 @@ static nsgif_error nsgif__parse_colour_table(
return NSGIF_OK;
}
ret = nsgif__colour_table_extract(gif,
if (decode == false) {
frame->colour_table_offset = *pos - gif->buf;
}
ret = nsgif__colour_table_extract(
gif->local_colour_table, &gif->colour_layout,
2 << (frame->flags & NSGIF_COLOUR_TABLE_SIZE_MASK),
pos, decode);
data, len, &used_bytes, decode);
if (ret != NSGIF_OK) {
return ret;
}
*pos += used_bytes;
if (decode) {
gif->colour_table = gif->local_colour_table;
} else {
frame->info.local_palette = true;
}
gif->colour_table = gif->local_colour_table;
return NSGIF_OK;
}
@ -1223,7 +1259,8 @@ static struct nsgif_frame *nsgif__get_frame(
frame = &gif->frames[frame_idx];
frame->transparency_index = NSGIF_NO_TRANSPARENCY;
frame->frame_pointer = gif->buf_pos;
frame->frame_offset = gif->buf_pos;
frame->info.local_palette = false;
frame->info.transparency = false;
frame->redraw_required = false;
frame->info.display = false;
@ -1261,7 +1298,7 @@ static nsgif_error nsgif__process_frame(
end = gif->buf + gif->buf_len;
if (decode) {
pos = gif->buf + frame->frame_pointer;
pos = gif->buf + frame->frame_offset;
/* Ensure this frame is supposed to be decoded */
if (frame->info.display == false) {
@ -1536,7 +1573,7 @@ static nsgif_error nsgif__parse_logical_screen_descriptor(
gif->info.width = data[0] | (data[1] << 8);
gif->info.height = data[2] | (data[3] << 8);
gif->global_colours = data[4] & NSGIF_COLOUR_TABLE_MASK;
gif->info.global_palette = data[4] & NSGIF_COLOUR_TABLE_MASK;
gif->colour_table_size = 2 << (data[4] & NSGIF_COLOUR_TABLE_SIZE_MASK);
gif->bg_index = data[5];
gif->aspect_ratio = data[6];
@ -1630,16 +1667,20 @@ nsgif_error nsgif_data_scan(
*/
if (gif->global_colour_table[0] == NSGIF_PROCESS_COLOURS) {
/* Check for a global colour map signified by bit 7 */
if (gif->global_colours) {
ret = nsgif__colour_table_extract(gif,
if (gif->info.global_palette) {
size_t remaining = gif->buf + gif->buf_len - nsgif_data;
size_t used;
ret = nsgif__colour_table_extract(
gif->global_colour_table,
&gif->colour_layout,
gif->colour_table_size,
&nsgif_data, true);
nsgif_data, remaining, &used, true);
if (ret != NSGIF_OK) {
return ret;
}
nsgif_data += used;
gif->buf_pos = (nsgif_data - gif->buf);
} else {
/* Create a default colour table with the first two
@ -1659,9 +1700,11 @@ nsgif_error nsgif_data_scan(
entry[gif->colour_layout.g] = 0xFF;
entry[gif->colour_layout.b] = 0xFF;
entry[gif->colour_layout.a] = 0xFF;
gif->colour_table_size = 2;
}
if (gif->global_colours &&
if (gif->info.global_palette &&
gif->bg_index < gif->colour_table_size) {
size_t bg_idx = gif->bg_index;
gif->info.background = gif->global_colour_table[bg_idx];
@ -1895,6 +1938,43 @@ const nsgif_frame_info_t *nsgif_get_frame_info(
return &gif->frames[frame].info;
}
/* exported function documented in nsgif.h */
void nsgif_global_palette(
const nsgif_t *gif,
uint32_t table[NSGIF_MAX_COLOURS],
size_t *entries)
{
size_t len = sizeof(*table) * NSGIF_MAX_COLOURS;
memcpy(table, gif->global_colour_table, len);
*entries = gif->colour_table_size;
}
/* exported function documented in nsgif.h */
bool nsgif_local_palette(
const nsgif_t *gif,
uint32_t frame,
uint32_t table[NSGIF_MAX_COLOURS],
size_t *entries)
{
const nsgif_frame *f;
if (frame >= gif->frame_count_partial) {
return false;
}
f = &gif->frames[frame];
if (f->info.local_palette == false) {
return false;
}
*entries = 2 << (f->flags & NSGIF_COLOUR_TABLE_SIZE_MASK);
nsgif__colour_table_decode(table, &gif->colour_layout,
*entries, gif->buf + f->colour_table_offset);
return true;
}
/* exported function documented in nsgif.h */
const char *nsgif_strerror(nsgif_error err)
{

View File

@ -23,6 +23,9 @@
/** Representation of infinity. */
#define NSGIF_INFINITE (UINT32_MAX)
/** Maximum colour table size */
#define NSGIF_MAX_COLOURS 256
/**
* Opaque type used by LibNSGIF to represent a GIF object in memory.
*/
@ -162,6 +165,11 @@ typedef enum nsgif_bitmap_fmt {
* but they are owned by a \ref nsgif_t.
*
* See \ref nsgif_bitmap_fmt for pixel format information.
*
* The bitmap may have a row_span greater than the bitmap width, but the
* difference between row span and width must be a whole number of pixels
* (a multiple of four bytes). If row span is greater than width, the
* \ref get_rowspan callback must be provided.
*/
typedef void nsgif_bitmap_t;
@ -186,7 +194,8 @@ typedef struct nsgif_bitmap_cb_vt {
/**
* Get pointer to pixel buffer in a bitmap.
*
* The pixel buffer must be `width * height * sizeof(uint32_t)`.
* The pixel buffer must be `(width + N) * height * sizeof(uint32_t)`.
* Where `N` is any number greater than or equal to 0.
* Note that the returned pointer to uint8_t must be 4-byte aligned.
*
* \param[in] bitmap The bitmap.
@ -218,6 +227,15 @@ typedef struct nsgif_bitmap_cb_vt {
* \param[in] bitmap The bitmap.
*/
void (*modified)(nsgif_bitmap_t *bitmap);
/**
* Get row span in pixels.
*
* If this callback is not provided, LibNSGIF will use the width.
*
* \param[in] bitmap The bitmap.
*/
uint32_t (*get_rowspan)(nsgif_bitmap_t *bitmap);
} nsgif_bitmap_cb_vt;
/**
@ -345,6 +363,8 @@ typedef struct nsgif_info {
int loop_max;
/** background colour in same pixel format as \ref nsgif_bitmap_t. */
uint32_t background;
/** whether the GIF has a global colour table */
bool global_palette;
} nsgif_info_t;
/**
@ -377,6 +397,8 @@ typedef struct nsgif_frame_info {
bool display;
/** whether the frame may have transparency */
bool transparency;
/** whether the frame has a local colour table */
bool local_palette;
/** Disposal method for previous frame; affects plotting */
uint8_t disposal;
@ -408,6 +430,43 @@ const nsgif_frame_info_t *nsgif_get_frame_info(
const nsgif_t *gif,
uint32_t frame);
/**
* Get the global colour palette.
*
* If the GIF has no global colour table, this will return the default
* colour palette.
*
* Colours in same pixel format as \ref nsgif_bitmap_t.
*
* \param[in] gif The \ref nsgif_t object.
* \param[out] table Client buffer to hold the colour table.
* \param[out] entries The number of used entries in the colour table.
*/
void nsgif_global_palette(
const nsgif_t *gif,
uint32_t table[NSGIF_MAX_COLOURS],
size_t *entries);
/**
* Get the local colour palette for a frame.
*
* Frames may have no local palette. In this case they use the global palette.
* This function returns false if the frame has no local palette.
*
* Colours in same pixel format as \ref nsgif_bitmap_t.
*
* \param[in] gif The \ref nsgif_t object.
* \param[in] frame The \ref frame to get the palette for.
* \param[out] table Client buffer to hold the colour table.
* \param[out] entries The number of used entries in the colour table.
* \return true if a palette is returned, false otherwise.
*/
bool nsgif_local_palette(
const nsgif_t *gif,
uint32_t frame,
uint32_t table[NSGIF_MAX_COLOURS],
size_t *entries);
/**
* Configure handling of small frame delays.
*

View File

@ -27,6 +27,7 @@ static struct nsgif_options {
const char *file;
const char *ppm;
uint64_t loops;
bool palette;
bool info;
} nsgif_options;
@ -53,6 +54,13 @@ static const struct cli_table_entry cli_entries[] = {
.d = "Loop through decoding all frames N times. "
"The default is 1."
},
{
.s = 'p',
.l = "palette",
.t = CLI_BOOL,
.v.b = &nsgif_options.palette,
.d = "Save palette images."
},
{
.p = true,
.l = "FILE",
@ -142,6 +150,7 @@ static void print_gif_info(const nsgif_info_t *info)
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, " global palette: %s\n", info->global_palette ? "yes" : "no");
fprintf(stdout, " background:\n");
fprintf(stdout, " red: 0x%"PRIx8"\n", bg[0]);
fprintf(stdout, " green: 0x%"PRIx8"\n", bg[1]);
@ -154,6 +163,7 @@ static void print_gif_frame_info(const nsgif_frame_info_t *info, uint32_t i)
const char *disposal = nsgif_str_disposal(info->disposal);
fprintf(stdout, " - frame: %"PRIu32"\n", i);
fprintf(stdout, " local palette: %s\n", info->local_palette ? "yes" : "no");
fprintf(stdout, " disposal-method: %s\n", disposal);
fprintf(stdout, " transparency: %s\n", info->transparency ? "yes" : "no");
fprintf(stdout, " display: %s\n", info->display ? "yes" : "no");
@ -165,7 +175,82 @@ static void print_gif_frame_info(const nsgif_frame_info_t *info, uint32_t i)
fprintf(stdout, " h: %"PRIu32"\n", info->rect.y1 - info->rect.y0);
}
static void decode(FILE* ppm, const char *name, nsgif_t *gif)
static bool save_palette(
const char *img_filename,
const char *palette_filename,
const uint32_t palette[NSGIF_MAX_COLOURS],
size_t used_entries)
{
enum {
SIZE = 32,
COUNT = 16,
};
FILE *f;
int size = COUNT * SIZE + 1;
f = fopen(palette_filename, "w+");
if (f == NULL) {
fprintf(stderr, "Unable to open %s for writing\n",
palette_filename);
return false;
}
fprintf(f, "P3\n");
fprintf(f, "# %s: %s\n", img_filename, palette_filename);
fprintf(f, "# Colour count: %zu\n", used_entries);
fprintf(f, "%u %u 256\n", size, size);
for (int y = 0; y < size; y++) {
for (int x = 0; x < size; x++) {
if (x % SIZE == 0 || y % SIZE == 0) {
fprintf(f, "0 0 0 ");
} else {
size_t offset = y / SIZE * COUNT + x / SIZE;
uint8_t *entry = (uint8_t *)&palette[offset];
fprintf(f, "%u %u %u ",
entry[0],
entry[1],
entry[2]);
}
}
fprintf(f, "\n");
}
fclose(f);
return true;
}
static bool save_global_palette(const nsgif_t *gif)
{
uint32_t table[NSGIF_MAX_COLOURS];
size_t entries;
nsgif_global_palette(gif, table, &entries);
return save_palette(nsgif_options.file, "global-palette.ppm",
table, entries);
}
static bool save_local_palette(const nsgif_t *gif, uint32_t frame)
{
static uint32_t table[NSGIF_MAX_COLOURS];
char filename[64];
size_t entries;
snprintf(filename, sizeof(filename), "local-palette-%"PRIu32".ppm",
frame);
if (!nsgif_local_palette(gif, frame, table, &entries)) {
return false;
}
return save_palette(nsgif_options.file, filename, table, entries);
}
static void decode(FILE* ppm, const char *name, nsgif_t *gif, bool first)
{
nsgif_error err;
uint32_t frame_prev = 0;
@ -173,7 +258,7 @@ static void decode(FILE* ppm, const char *name, nsgif_t *gif)
info = nsgif_get_info(gif);
if (ppm != NULL) {
if (first && ppm != NULL) {
fprintf(ppm, "P3\n");
fprintf(ppm, "# %s\n", name);
fprintf(ppm, "# width %u \n", info->width);
@ -184,9 +269,12 @@ static void decode(FILE* ppm, const char *name, nsgif_t *gif)
info->height * info->frame_count);
}
if (nsgif_options.info == true) {
if (first && nsgif_options.info) {
print_gif_info(info);
}
if (first && nsgif_options.palette && info->global_palette) {
save_global_palette(gif);
}
/* decode the frames */
while (true) {
@ -210,7 +298,7 @@ static void decode(FILE* ppm, const char *name, nsgif_t *gif)
}
frame_prev = frame_new;
if (nsgif_options.info == true) {
if (first && nsgif_options.info) {
const nsgif_frame_info_t *f_info;
f_info = nsgif_get_frame_info(gif, frame_new);
@ -218,6 +306,9 @@ static void decode(FILE* ppm, const char *name, nsgif_t *gif)
print_gif_frame_info(f_info, frame_new);
}
}
if (first && nsgif_options.palette) {
save_local_palette(gif, frame_new);
}
err = nsgif_frame_decode(gif, frame_new, &bitmap);
if (err != NSGIF_OK) {
@ -226,7 +317,7 @@ static void decode(FILE* ppm, const char *name, nsgif_t *gif)
frame_new, nsgif_strerror(err));
/* Continue decoding the rest of the frames. */
} else if (ppm != NULL) {
} else if (first && ppm != NULL) {
fprintf(ppm, "# frame %u:\n", frame_new);
image = (const uint8_t *) bitmap;
for (uint32_t y = 0; y != info->height; y++) {
@ -299,7 +390,10 @@ int main(int argc, char *argv[])
}
for (uint64_t i = 0; i < nsgif_options.loops; i++) {
decode((i == 0) ? ppm : NULL, nsgif_options.file, gif);
decode(ppm, nsgif_options.file, gif, i == 0);
/* We want to ignore any loop limit in the GIF. */
nsgif_reset(gif);
}
if (ppm != NULL) {

View File

@ -201,6 +201,7 @@ print_frame( const nsgif_frame_info_t *frame_info )
printf( "frame_info:\n" );
printf( " display = %d\n", frame_info->display );
printf( " local_palette = %d\n", frame_info->local_palette );
printf( " transparency = %d\n", frame_info->transparency );
printf( " disposal = %d (%s)\n",
frame_info->disposal,
@ -222,6 +223,7 @@ print_animation( nsgif_t *anim, const nsgif_info_t *info )
printf( " width = %d\n", info->width );
printf( " height = %d\n", info->height );
printf( " frame_count = %d\n", info->frame_count );
printf( " global_palette = %d\n", info->global_palette );
printf( " loop_max = %d\n", info->loop_max );
printf( " background = %d %d %d %d\n",
bg[0], bg[1], bg[2], bg[3] );