diff --git a/libvips/foreign/libnsgif/README.md b/libvips/foreign/libnsgif/README.md index 10ff2405..d1b49e6c 100644 --- a/libvips/foreign/libnsgif/README.md +++ b/libvips/foreign/libnsgif/README.md @@ -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 diff --git a/libvips/foreign/libnsgif/gif.c b/libvips/foreign/libnsgif/gif.c index 84d4209b..d2b6054e 100644 --- a/libvips/foreign/libnsgif/gif.c +++ b/libvips/foreign/libnsgif/gif.c @@ -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) { diff --git a/libvips/foreign/libnsgif/nsgif.h b/libvips/foreign/libnsgif/nsgif.h index a02597c3..208bc27c 100644 --- a/libvips/foreign/libnsgif/nsgif.h +++ b/libvips/foreign/libnsgif/nsgif.h @@ -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. * diff --git a/libvips/foreign/libnsgif/test/nsgif.c b/libvips/foreign/libnsgif/test/nsgif.c index 61c63d0d..4a9e31c5 100644 --- a/libvips/foreign/libnsgif/test/nsgif.c +++ b/libvips/foreign/libnsgif/test/nsgif.c @@ -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) { diff --git a/libvips/foreign/nsgifload.c b/libvips/foreign/nsgifload.c index 82be90c3..73c26f7b 100644 --- a/libvips/foreign/nsgifload.c +++ b/libvips/foreign/nsgifload.c @@ -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] );