From 9bdf5e8cda3e0c63584984282b1e36d97c50bb1a Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 1 Apr 2021 12:53:57 +0100 Subject: [PATCH 01/41] try delaying libnsgif alloc until read frame gives the caller a chance to detect memory bombs --- libvips/foreign/libnsgif/libnsgif.c | 41 +++++++++++------------------ 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/libvips/foreign/libnsgif/libnsgif.c b/libvips/foreign/libnsgif/libnsgif.c index 55c25999..679522bd 100644 --- a/libvips/foreign/libnsgif/libnsgif.c +++ b/libvips/foreign/libnsgif/libnsgif.c @@ -79,34 +79,17 @@ gif_initialise_sprite(gif_animation *gif, unsigned int width, unsigned int height) { - unsigned int max_width; - unsigned int max_height; - struct bitmap *buffer; - - /* Check if we've changed */ - if ((width <= gif->width) && (height <= gif->height)) { + /* Already allocated? */ + if (gif->frame_image) { return GIF_OK; } - /* Get our maximum values */ - max_width = (width > gif->width) ? width : gif->width; - max_height = (height > gif->height) ? height : gif->height; - - /* Allocate some more memory */ assert(gif->bitmap_callbacks.bitmap_create); - buffer = gif->bitmap_callbacks.bitmap_create(max_width, max_height); - if (buffer == NULL) { + gif->frame_image = gif->bitmap_callbacks.bitmap_create(width, height); + if (gif->frame_image == NULL) { return GIF_INSUFFICIENT_MEMORY; } - assert(gif->bitmap_callbacks.bitmap_destroy); - gif->bitmap_callbacks.bitmap_destroy(gif->frame_image); - gif->frame_image = buffer; - gif->width = max_width; - gif->height = max_height; - - /* Invalidate our currently decoded image */ - gif->decoded_frame = GIF_INVALID_FRAME; return GIF_OK; } @@ -392,10 +375,12 @@ static gif_result gif_initialise_frame(gif_animation *gif) gif->frames[frame].redraw_required = ((gif->frames[frame].disposal_method == GIF_FRAME_CLEAR) || (gif->frames[frame].disposal_method == GIF_FRAME_RESTORE)); - /* Boundary checking - shouldn't ever happen except with junk data */ - if (gif_initialise_sprite(gif, (offset_x + width), (offset_y + height))) { - return GIF_INSUFFICIENT_MEMORY; - } + /* Frame size may have grown. + */ + gif->width = (offset_x + width > gif->width) ? + offset_x + width : gif->width; + gif->height = (offset_y + height > gif->height) ? + offset_y + height : gif->height; /* Decode the flags */ flags = gif_data[9]; @@ -739,6 +724,12 @@ gif_internal_decode_frame(gif_animation *gif, goto gif_decode_frame_exit; } + /* Make sure we have a buffer to decode to. + */ + if (gif_initialise_sprite(gif, gif->width, gif->height)) { + return GIF_INSUFFICIENT_MEMORY; + } + /* Decode the flags */ flags = gif_data[9]; colour_table_size = 2 << (flags & GIF_COLOUR_TABLE_SIZE_MASK); From 701dcc750043d5207cd48e4e993e0534a265df5e Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Thu, 1 Apr 2021 19:18:56 +0100 Subject: [PATCH 02/41] nsgifload: enforce maximum GIF dimensions of 16383 The GIF spec allows dimensions up to 65535 (16-bit unsigned), but this equates to 17GB/frame. A common task is to convert animated GIF to animated WebP, and the latter supports dimensions up to 16383 (14-bit unsigned), so that seems like a sensible limit and equates to 1GB/frame. Also makes the error message consistent with other loaders. --- libvips/foreign/nsgifload.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libvips/foreign/nsgifload.c b/libvips/foreign/nsgifload.c index 4b449e80..99c9234b 100644 --- a/libvips/foreign/nsgifload.c +++ b/libvips/foreign/nsgifload.c @@ -534,17 +534,17 @@ vips_foreign_load_nsgif_class_init( VipsForeignLoadNsgifClass *class ) static void * vips_foreign_load_nsgif_bitmap_create( int width, int height ) { - /* Check GIF dimensions fit within 16-bit unsigned. + /* Enforce max GIF dimensions of 16383 (0x7FFF). */ if( width <= 0 || - width > 65535 || + width > 16383 || height <= 0 || - height > 65535 ) { + height > 16383 ) { vips_error( "gifload", - "%s", _( "dimensions out of range ") ); + "%s", _( "bad image dimensions") ); return( NULL ); } - return g_malloc0( width * height * 4 ); + return g_malloc0( (gsize) width * height * 4 ); } static void From 07e73475c18e129e18825426b3071b99f6b6871a Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 2 Apr 2021 12:00:50 +0100 Subject: [PATCH 03/41] make a patch for the libnsgif alloc delay --- .../libnsgif/patches/delay-alloc.patch | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 libvips/foreign/libnsgif/patches/delay-alloc.patch diff --git a/libvips/foreign/libnsgif/patches/delay-alloc.patch b/libvips/foreign/libnsgif/patches/delay-alloc.patch new file mode 100644 index 00000000..d7d5f97d --- /dev/null +++ b/libvips/foreign/libnsgif/patches/delay-alloc.patch @@ -0,0 +1,71 @@ +--- libnsgif-orig.c 2021-04-02 11:59:38.120048459 +0100 ++++ libnsgif.c 2021-04-02 11:59:44.439972362 +0100 +@@ -79,34 +79,17 @@ + unsigned int width, + unsigned int height) + { +- unsigned int max_width; +- unsigned int max_height; +- struct bitmap *buffer; +- +- /* Check if we've changed */ +- if ((width <= gif->width) && (height <= gif->height)) { ++ /* Already allocated? */ ++ if (gif->frame_image) { + return GIF_OK; + } + +- /* Get our maximum values */ +- max_width = (width > gif->width) ? width : gif->width; +- max_height = (height > gif->height) ? height : gif->height; +- +- /* Allocate some more memory */ + assert(gif->bitmap_callbacks.bitmap_create); +- buffer = gif->bitmap_callbacks.bitmap_create(max_width, max_height); +- if (buffer == NULL) { ++ gif->frame_image = gif->bitmap_callbacks.bitmap_create(width, height); ++ if (gif->frame_image == NULL) { + return GIF_INSUFFICIENT_MEMORY; + } + +- assert(gif->bitmap_callbacks.bitmap_destroy); +- gif->bitmap_callbacks.bitmap_destroy(gif->frame_image); +- gif->frame_image = buffer; +- gif->width = max_width; +- gif->height = max_height; +- +- /* Invalidate our currently decoded image */ +- gif->decoded_frame = GIF_INVALID_FRAME; + return GIF_OK; + } + +@@ -392,10 +375,12 @@ + gif->frames[frame].redraw_required = ((gif->frames[frame].disposal_method == GIF_FRAME_CLEAR) || + (gif->frames[frame].disposal_method == GIF_FRAME_RESTORE)); + +- /* Boundary checking - shouldn't ever happen except with junk data */ +- if (gif_initialise_sprite(gif, (offset_x + width), (offset_y + height))) { +- return GIF_INSUFFICIENT_MEMORY; +- } ++ /* Frame size may have grown. ++ */ ++ gif->width = (offset_x + width > gif->width) ? ++ offset_x + width : gif->width; ++ gif->height = (offset_y + height > gif->height) ? ++ offset_y + height : gif->height; + + /* Decode the flags */ + flags = gif_data[9]; +@@ -739,6 +724,12 @@ + goto gif_decode_frame_exit; + } + ++ /* Make sure we have a buffer to decode to. ++ */ ++ if (gif_initialise_sprite(gif, gif->width, gif->height)) { ++ return GIF_INSUFFICIENT_MEMORY; ++ } ++ + /* Decode the flags */ + flags = gif_data[9]; + colour_table_size = 2 << (flags & GIF_COLOUR_TABLE_SIZE_MASK); From f726edb7f71f05f2af6f08a7442354c1b70e9383 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 3 Apr 2021 12:28:41 +0100 Subject: [PATCH 04/41] remove stray bitmap_create call missed this one in the delay gif alloc patch --- libvips/foreign/libnsgif/libnsgif.c | 8 -------- .../libnsgif/patches/delay-alloc.patch | 19 +++++++++++++++++-- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/libvips/foreign/libnsgif/libnsgif.c b/libvips/foreign/libnsgif/libnsgif.c index 679522bd..1c4bd00e 100644 --- a/libvips/foreign/libnsgif/libnsgif.c +++ b/libvips/foreign/libnsgif/libnsgif.c @@ -1106,14 +1106,6 @@ gif_result gif_initialise(gif_animation *gif, size_t size, unsigned char *data) } gif->frame_holders = 1; - /* Initialise the bitmap header */ - assert(gif->bitmap_callbacks.bitmap_create); - gif->frame_image = gif->bitmap_callbacks.bitmap_create(gif->width, gif->height); - if (gif->frame_image == NULL) { - gif_finalise(gif); - return GIF_INSUFFICIENT_MEMORY; - } - /* Remember we've done this now */ gif->buffer_position = gif_data - gif->gif_data; } diff --git a/libvips/foreign/libnsgif/patches/delay-alloc.patch b/libvips/foreign/libnsgif/patches/delay-alloc.patch index d7d5f97d..21630f2b 100644 --- a/libvips/foreign/libnsgif/patches/delay-alloc.patch +++ b/libvips/foreign/libnsgif/patches/delay-alloc.patch @@ -1,5 +1,5 @@ ---- libnsgif-orig.c 2021-04-02 11:59:38.120048459 +0100 -+++ libnsgif.c 2021-04-02 11:59:44.439972362 +0100 +--- libnsgif-orig.c 2021-04-03 12:23:43.700260070 +0100 ++++ libnsgif.c 2021-04-03 12:24:44.079567702 +0100 @@ -79,34 +79,17 @@ unsigned int width, unsigned int height) @@ -69,3 +69,18 @@ /* Decode the flags */ flags = gif_data[9]; colour_table_size = 2 << (flags & GIF_COLOUR_TABLE_SIZE_MASK); +@@ -1115,14 +1106,6 @@ + } + gif->frame_holders = 1; + +- /* Initialise the bitmap header */ +- assert(gif->bitmap_callbacks.bitmap_create); +- gif->frame_image = gif->bitmap_callbacks.bitmap_create(gif->width, gif->height); +- if (gif->frame_image == NULL) { +- gif_finalise(gif); +- return GIF_INSUFFICIENT_MEMORY; +- } +- + /* Remember we've done this now */ + gif->buffer_position = gif_data - gif->gif_data; + } From 234c4684894adca556071d6aebcfeb47db9e967a Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 3 Apr 2021 16:32:23 +0100 Subject: [PATCH 05/41] comment fix --- libvips/foreign/tiffsave.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libvips/foreign/tiffsave.c b/libvips/foreign/tiffsave.c index 57eda43b..f9a4db2e 100644 --- a/libvips/foreign/tiffsave.c +++ b/libvips/foreign/tiffsave.c @@ -115,7 +115,7 @@ G_DEFINE_ABSTRACT_TYPE( VipsForeignSaveTiff, vips_foreign_save_tiff, #define UC VIPS_FORMAT_UCHAR -/* Type promotion for save ... just always go to uchar. +/* Type promotion for jpeg-in-tiff save ... just always go to uchar. */ static int bandfmt_jpeg[10] = { /* UC C US S UI I F X D DX */ From 930078a6f971ccc4abf5568d384975c36fbad54c Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 3 Apr 2021 18:50:18 +0100 Subject: [PATCH 06/41] note jp2k support in README --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1e7cc567..0c512df1 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ operations, frequency filtering, colour, resampling, statistics and others. It supports a large range of [numeric types](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsBandFormat), from 8-bit int to 128-bit complex. Images can have any number of bands. -It supports a good range of image formats, including JPEG, TIFF, PNG, +It supports a good range of image formats, including JPEG, JPEG2000, TIFF, PNG, WebP, HEIC, AVIF, FITS, Matlab, OpenEXR, PDF, SVG, HDR, PPM / PGM / PFM, CSV, GIF, Analyze, NIfTI, DeepZoom, and OpenSlide. It can also load images via ImageMagick or GraphicsMagick, letting it work with formats like DICOM. @@ -274,6 +274,10 @@ If available, vips can load and save NIfTI images. If available, libvips will directly read (but not write, sadly) OpenEXR images. +### OpenJPEG + +If available, libvips will read and write JPEG2000 images. + ### OpenSlide If available, libvips can load OpenSlide-supported virtual slide From d56a47829b2180558ed9a913030053e110f37394 Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Wed, 7 Apr 2021 21:44:31 +0100 Subject: [PATCH 07/41] colour: add a fallback for the increasingly-common P3 profile Uses the CC0-licensed 'magic' compatibility profile from https://github.com/saucecontrol/Compact-ICC-Profiles#display-p3 --- libvips/colour/profiles.c | 59 +++++++++++++++++++++++++++++++++ libvips/colour/profiles/p3.icm | Bin 0 -> 736 bytes 2 files changed, 59 insertions(+) create mode 100644 libvips/colour/profiles/p3.icm diff --git a/libvips/colour/profiles.c b/libvips/colour/profiles.c index 521faa98..76b5d62f 100644 --- a/libvips/colour/profiles.c +++ b/libvips/colour/profiles.c @@ -32143,6 +32143,64 @@ static VipsProfileFallback vips__profile_fallback_cmyk = { } }; +static VipsProfileFallback vips__profile_fallback_p3 = { + "p3", + 736, + { + 0x78, 0xDA, 0x7D, 0xD0, 0x03, 0xA0, 0xE4, 0x30, 0x14, 0x05, 0xD0, 0x57, + 0x7C, 0xDB, 0xB6, 0x6D, 0xDB, 0xB6, 0x8D, 0xB1, 0xD1, 0xCE, 0xDA, 0xB6, + 0x6D, 0xDB, 0xB6, 0x6D, 0xDB, 0xB6, 0xED, 0x74, 0xED, 0x5B, 0x9D, 0xC6, + 0x09, 0x00, 0x7E, 0x49, 0xCC, 0x92, 0x50, 0xB8, 0x21, 0x80, 0x44, 0x4A, + 0x2B, 0x8B, 0x33, 0x92, 0x1D, 0x2B, 0xAB, 0xAA, 0x1D, 0x35, 0xAE, 0x00, + 0x01, 0xA6, 0xA0, 0x05, 0xFA, 0x60, 0xD7, 0xC4, 0xA2, 0xE4, 0x79, 0x25, + 0xE9, 0xA5, 0x80, 0x42, 0x35, 0xB5, 0xA4, 0x58, 0xB4, 0x52, 0x0C, 0xBF, + 0xE4, 0xC5, 0x09, 0xC0, 0x98, 0xEF, 0x51, 0x3F, 0x7E, 0x93, 0x94, 0xDD, + 0x56, 0x66, 0xB0, 0xF7, 0x52, 0x5A, 0x52, 0x78, 0x42, 0x41, 0x6A, 0xD8, + 0x5E, 0xEB, 0x1E, 0x56, 0xF0, 0xFF, 0x68, 0xB1, 0x39, 0x14, 0x0B, 0x7D, + 0x1F, 0xA1, 0xA7, 0x9E, 0x25, 0x57, 0xD2, 0x00, 0x98, 0x2E, 0xB2, 0x6E, + 0x4B, 0x5A, 0xCE, 0xD8, 0x12, 0xD9, 0x54, 0x89, 0x16, 0x85, 0xEC, 0xCB, + 0x98, 0xF7, 0xC5, 0x89, 0x8C, 0x9B, 0xBF, 0xB8, 0x94, 0xB1, 0xB2, 0xB4, + 0x38, 0x05, 0x99, 0x8F, 0x9E, 0x56, 0xBC, 0x9F, 0xDC, 0xFC, 0x93, 0xBF, + 0xCE, 0xC5, 0x44, 0x8D, 0x2A, 0x0C, 0x41, 0xE5, 0x3F, 0x42, 0x73, 0x5A, + 0xD1, 0xCC, 0x37, 0x25, 0x25, 0x10, 0x98, 0x33, 0xF8, 0x52, 0xFA, 0xB4, + 0x08, 0x30, 0xF4, 0xC1, 0xCC, 0xF7, 0xFD, 0x28, 0xEB, 0x7A, 0x11, 0x20, + 0x6E, 0x23, 0xA3, 0x1F, 0x65, 0xD9, 0xE8, 0x7F, 0x71, 0x04, 0x80, 0xF6, + 0xF2, 0x1F, 0x65, 0x9E, 0x91, 0x00, 0x46, 0xDA, 0x00, 0xBB, 0x5A, 0xB1, + 0x54, 0xCA, 0x16, 0xF0, 0x25, 0x2B, 0xD0, 0x63, 0x0B, 0x91, 0x50, 0x0A, + 0x72, 0xE8, 0x07, 0xB3, 0x60, 0x1B, 0x5C, 0xC6, 0x00, 0x73, 0xC6, 0xD2, + 0x31, 0x09, 0x36, 0x1C, 0xDB, 0x8C, 0x3D, 0xC2, 0x1D, 0xF1, 0x32, 0xBC, + 0x2F, 0xBE, 0x8D, 0xC0, 0x88, 0x04, 0xA2, 0x0B, 0xB1, 0x83, 0xD4, 0x27, + 0xAB, 0xC8, 0x99, 0xE4, 0x6B, 0xB5, 0x5C, 0xB5, 0x69, 0x6A, 0x1F, 0xD4, + 0x6B, 0xD5, 0x37, 0x68, 0x38, 0x69, 0xF4, 0xD1, 0x78, 0xA6, 0xC9, 0xD2, + 0x3C, 0xAE, 0x95, 0xA5, 0xB5, 0x55, 0x3B, 0x41, 0x7B, 0xA3, 0x4E, 0xB2, + 0xCE, 0x6E, 0xDD, 0x52, 0xDD, 0xCB, 0x7A, 0xB4, 0xBE, 0x96, 0xFE, 0x14, + 0x83, 0x44, 0x83, 0x4B, 0x86, 0xDD, 0x8D, 0xFC, 0x8C, 0xCE, 0x18, 0xF7, + 0x31, 0x89, 0x37, 0x79, 0x61, 0xBA, 0xC4, 0x4C, 0x61, 0x1E, 0x6E, 0xFE, + 0xDE, 0x62, 0x8F, 0xE5, 0x68, 0x2B, 0xA9, 0x75, 0xBA, 0x8D, 0xB3, 0x2D, + 0x61, 0x7B, 0xD3, 0xEE, 0x90, 0xFD, 0x7A, 0x87, 0x05, 0x8E, 0xD3, 0x9C, + 0x26, 0x38, 0x8F, 0x73, 0x99, 0xE8, 0x3A, 0xDD, 0x6D, 0xB1, 0xFB, 0x26, + 0x8F, 0xE3, 0x9E, 0xF7, 0xBD, 0xB5, 0x7D, 0x7C, 0x7C, 0x8B, 0xFC, 0xDA, + 0xF9, 0xCF, 0x09, 0xB8, 0x10, 0x64, 0x1E, 0x5C, 0x1C, 0x32, 0x2C, 0xF4, + 0x74, 0xB8, 0x6B, 0x04, 0x15, 0xB9, 0x23, 0xDA, 0x31, 0xA6, 0x5D, 0xEC, + 0xC5, 0xF8, 0xB4, 0x84, 0x45, 0x49, 0x4E, 0xC9, 0x23, 0x52, 0x0D, 0xD3, + 0x06, 0x66, 0x18, 0x67, 0x8E, 0xCD, 0xF6, 0xCC, 0x59, 0x9B, 0x57, 0x94, + 0xFF, 0xA0, 0x70, 0x60, 0x71, 0x58, 0xC9, 0xA5, 0xB2, 0x41, 0x15, 0x69, + 0x55, 0x50, 0xBD, 0xA1, 0xB6, 0x5B, 0x7D, 0x6E, 0xA3, 0x4D, 0xD3, 0x43, + 0xD6, 0x4E, 0xCE, 0x0C, 0x5E, 0x6F, 0x81, 0x42, 0xD4, 0x20, 0x29, 0x92, + 0x65, 0x2B, 0xD2, 0xA9, 0x6C, 0x55, 0x51, 0xCB, 0xFA, 0xD6, 0x8A, 0xB6, + 0xBD, 0xDA, 0xCF, 0xE8, 0xB8, 0xAB, 0xF3, 0xA3, 0x6E, 0xF6, 0x3D, 0x0A, + 0x7B, 0xF5, 0xEE, 0xB3, 0xBD, 0xBF, 0xD6, 0xC0, 0xA2, 0xC1, 0x13, 0x87, + 0x3E, 0x1D, 0x91, 0x33, 0x6A, 0xEE, 0x58, 0xFD, 0xF1, 0xAD, 0x26, 0xDE, + 0x9C, 0x52, 0x33, 0xED, 0xC4, 0xCC, 0xF2, 0xD9, 0x67, 0xE7, 0xF1, 0x16, + 0xBC, 0x5C, 0x3C, 0x70, 0x99, 0xF7, 0x8A, 0x7D, 0xAB, 0xE9, 0x75, 0x4E, + 0x1B, 0x8E, 0x6F, 0xEE, 0xBF, 0x2D, 0x77, 0xA7, 0xE1, 0xEE, 0x33, 0xFB, + 0xE6, 0x1E, 0xEC, 0x78, 0xA4, 0xF6, 0x78, 0xDC, 0x29, 0xD7, 0xB3, 0xC6, + 0x17, 0xC8, 0x4B, 0x1F, 0xAE, 0xBE, 0xBB, 0x89, 0xDD, 0xD1, 0xBD, 0x6F, + 0xFB, 0x28, 0xF4, 0x69, 0xE1, 0x0B, 0xEA, 0xF5, 0xD8, 0x77, 0x7B, 0x3E, + 0x7E, 0xFC, 0x04, 0xD4, 0xCA, 0xD2, 0x46, + } +}; + static VipsProfileFallback vips__profile_fallback_sRGB = { "sRGB", 6922, @@ -32348,6 +32406,7 @@ static VipsProfileFallback vips__profile_fallback_sRGB = { VipsProfileFallback *vips__profile_fallback_table[] = { &vips__profile_fallback_cmyk, + &vips__profile_fallback_p3, &vips__profile_fallback_sRGB, NULL }; diff --git a/libvips/colour/profiles/p3.icm b/libvips/colour/profiles/p3.icm new file mode 100644 index 0000000000000000000000000000000000000000..cfe315d39333cd92c907dafdeeb1350b780ac182 GIT binary patch literal 736 zcmZQzV0w^~oLkH!z`&53S5g$@?xYYA8KuDfh=G|wgn^TRk3lvuxwybL*e!$s2#OQS zi<3)=azJbZ_;!tf5yZQ!n~|88Qk~C#`hlyXnZ3V@>1nApNrXDAEY6hF;$)!A2Oy41 zE+{HtU|{3{vU$o&3P5ZLAX}se=qMmt2gFW?upL0`BnTViHj$!`AZMVO3?QyZM`9-- zv7vSWl{2sw2N(m@BS1-N1<*bQ24`mj22dD4WIhLi{l+ME1|imbpMk-4KS&-S<^|+$ zG-qJo+KLd|Z#NIj(Yg zavtWg=i1Nh#C?({gy$h|2_GllB7O({2Li2vx`MZax`ge7zlm%TEfg~o`zwA*Vy0xC zl$*4Y471F0*$Z-e<<}`JQJkkVM|r-=Qq_%W2h^`>yw&2;*47Est4ssH~0)XLIE literal 0 HcmV?d00001 From db22eb4d4f867e4eccf6374d1640160fb152fd67 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 8 Apr 2021 10:53:15 +0100 Subject: [PATCH 08/41] add p3.icm to makefile.am and fix make dist --- libvips/colour/profiles/Makefile.am | 1 + po/POTFILES.in | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/libvips/colour/profiles/Makefile.am b/libvips/colour/profiles/Makefile.am index fc8f9734..b9d87a46 100644 --- a/libvips/colour/profiles/Makefile.am +++ b/libvips/colour/profiles/Makefile.am @@ -1,3 +1,4 @@ EXTRA_DIST = \ + p3.icm \ cmyk.icm \ sRGB.icm diff --git a/po/POTFILES.in b/po/POTFILES.in index 6152da50..87cbcb90 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -2,7 +2,6 @@ cplusplus/include/vips/VConnection8.h cplusplus/include/vips/VError8.h cplusplus/include/vips/VImage8.h cplusplus/include/vips/VInterpolate8.h -cplusplus/include/vips/vips-operators.h libvips/include/vips/arithmetic.h libvips/include/vips/basic.h libvips/include/vips/buf.h From 8412b8233c8f37ab00dc85f4e9976df191da242c Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 12 Apr 2021 10:59:50 +0100 Subject: [PATCH 09/41] start adding colour text rendering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit try eg. vips text x.png 😀 --rgba --dpi 300 But it's not working :( need to tyweak something else? --- libvips/create/text.c | 57 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/libvips/create/text.c b/libvips/create/text.c index a750c8ec..bfa6dd4d 100644 --- a/libvips/create/text.c +++ b/libvips/create/text.c @@ -94,6 +94,7 @@ typedef struct _VipsText { gboolean justify; int dpi; char *fontfile; + gboolean rgba; FT_Bitmap bitmap; PangoContext *context; @@ -342,6 +343,9 @@ vips_text_build( VipsObject *object ) VipsText *text = (VipsText *) object; VipsRect extents; + int bands; + VipsInterpretation interpretation; + FT_Pixel_Mode pixel_mode; int y; if( VIPS_OBJECT_CLASS( vips_text_parent_class )->build( object ) ) @@ -399,8 +403,19 @@ vips_text_build( VipsObject *object ) return( -1 ); } + if( text->rgba ) { + interpretation = VIPS_INTERPRETATION_sRGB; + bands = 4; + pixel_mode = FT_PIXEL_MODE_BGRA; + } + else { + interpretation = VIPS_INTERPRETATION_MULTIBAND; + bands = 1; + pixel_mode = FT_PIXEL_MODE_GRAY; + } + text->bitmap.width = extents.width; - text->bitmap.pitch = (text->bitmap.width + 3) & ~3; + text->bitmap.pitch = (bands * text->bitmap.width + 3) & ~3; text->bitmap.rows = extents.height; if( !(text->bitmap.buffer = VIPS_ARRAY( NULL, @@ -409,25 +424,31 @@ vips_text_build( VipsObject *object ) return( -1 ); } text->bitmap.num_grays = 256; - text->bitmap.pixel_mode = ft_pixel_mode_grays; + text->bitmap.pixel_mode = pixel_mode; memset( text->bitmap.buffer, 0x00, text->bitmap.pitch * text->bitmap.rows ); pango_ft2_render_layout( &text->bitmap, text->layout, -extents.left, -extents.top ); + /* Set DPI as pixels/mm. + */ + vips_image_init_fields( create->out, + text->bitmap.width, text->bitmap.rows, bands, + VIPS_FORMAT_UCHAR, VIPS_CODING_NONE, + interpretation, text->dpi / 25.4, text->dpi / 25.4 ); + g_mutex_unlock( vips_text_lock ); - vips_image_init_fields( create->out, - text->bitmap.width, text->bitmap.rows, 1, - VIPS_FORMAT_UCHAR, VIPS_CODING_NONE, - VIPS_INTERPRETATION_MULTIBAND, - 1.0, 1.0 ); - vips_image_pipelinev( create->out, - VIPS_DEMAND_STYLE_ANY, NULL ); + vips_image_pipelinev( create->out, VIPS_DEMAND_STYLE_ANY, NULL ); create->out->Xoffset = extents.left; create->out->Yoffset = extents.top; + if( text->rgba ) { + /* Convert premultiplied BGRA to RGBA. + */ + } + for( y = 0; y < text->bitmap.rows; y++ ) if( vips_image_write_line( create->out, y, (VipsPel *) text->bitmap.buffer + @@ -534,6 +555,13 @@ vips_text_class_init( VipsTextClass *class ) G_STRUCT_OFFSET( VipsText, fontfile ), NULL ); + VIPS_ARG_BOOL( class, "rgba", 9, + _( "RGBA" ), + _( "Enable RGBA output" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsText, rgba ), + FALSE ); + } static void @@ -563,17 +591,22 @@ vips_text_init( VipsText *text ) * * @justify: %gboolean, justify lines * * @dpi: %gint, render at this resolution * * @autofit_dpi: %gint, read out auto-fitted DPI + * * @rgba: %gboolean, enable RGBA output * * @spacing: %gint, space lines by this in points * - * Draw the string @text to an image. @out is a one-band 8-bit + * Draw the string @text to an image. @out is normally a one-band 8-bit * unsigned char image, with 0 for no text and 255 for text. Values between * are used for anti-aliasing. * + * Set @rgba to enable RGBA output. This is useful for colour emoji rendering, + * or support for pango markup features like `Red!`. + * * @text is the text to render as a UTF-8 string. It can contain Pango markup, - * for example "<i>The</i>Guardian". + * for example `TheGuardian`. * * @font is the font to render with, as a fontconfig name. Examples might be - * "sans 12" or perhaps "bitstream charter bold 10". + * `sans 12` or perhaps `bitstream charter bold 10`. * * You can specify a font to load with @fontfile. You'll need to also set the * name of the font with @font. From f53959b82475946a3f9b29d0ac98d34a9b906d5f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 12 Apr 2021 15:11:18 +0100 Subject: [PATCH 10/41] switch font rendering to pangocairo to make colour font rendering work --- ChangeLog | 1 + configure.ac | 30 +++++++------- libvips/create/create.c | 8 ++-- libvips/create/text.c | 88 ++++++++++++++++++++++------------------- 4 files changed, 67 insertions(+), 60 deletions(-) diff --git a/ChangeLog b/ChangeLog index 67264ff7..ef45770b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -22,6 +22,7 @@ - add vips_image_[set|get]_array_double() - add GIF load with libnsgif - add JPEG2000 load and save +- add "rgba" flag to vips_text() to enable full colour text rendering 22/12/20 start 8.10.6 - don't seek on bad file descriptors [kleisauke] diff --git a/configure.ac b/configure.ac index 9f906b8c..1a7c5ace 100644 --- a/configure.ac +++ b/configure.ac @@ -1083,25 +1083,25 @@ fi VIPS_CFLAGS="$VIPS_CFLAGS $LIBWEBP_CFLAGS" VIPS_LIBS="$VIPS_LIBS $LIBWEBP_LIBS" -# pangoft2 -AC_ARG_WITH([pangoft2], - AS_HELP_STRING([--without-pangoft2], - [build without pangoft2 (default: test)])) +# pangocairo for text rendering +AC_ARG_WITH([pangocairo], + AS_HELP_STRING([--without-pangocairo], + [build without pangocairo (default: test)])) -if test x"$with_pangoft2" != x"no"; then - PKG_CHECK_MODULES(PANGOFT2, pangoft2, - [AC_DEFINE(HAVE_PANGOFT2,1,[define if you have pangoft2 installed.]) - with_pangoft2=yes - PACKAGES_USED="$PACKAGES_USED pangoft2" +if test x"$with_pangocairo" != x"no"; then + PKG_CHECK_MODULES(PANGOCAIRO, pangocairo, + [AC_DEFINE(HAVE_PANGOCAIRO,1,[define if you have pangocairo installed.]) + with_pangocairo=yes + PACKAGES_USED="$PACKAGES_USED pangocairo" ], - [AC_MSG_WARN([pangoft2 not found; disabling pangoft2 support]) - with_pangoft2=no + [AC_MSG_WARN([pangocairo not found; disabling pangocairo support]) + with_pangocairo=no ] ) fi -VIPS_CFLAGS="$VIPS_CFLAGS $PANGOFT2_CFLAGS" -VIPS_LIBS="$VIPS_LIBS $PANGOFT2_LIBS" +VIPS_CFLAGS="$VIPS_CFLAGS $PANGOCAIRO_CFLAGS" +VIPS_LIBS="$VIPS_LIBS $PANGOCAIRO_LIBS" # look for TIFF with pkg-config ... fall back to our tester # pkgconfig support for libtiff starts with libtiff-4 @@ -1325,7 +1325,7 @@ use fftw3 for FFT: $with_fftw, \ accelerate loops with orc: $with_orc, \ ICC profile support with lcms: $with_lcms, \ zlib: $with_zlib, \ -text rendering with pangoft2: $with_pangoft2, \ +text rendering with pangocairo: $with_pangocairo, \ EXIF metadata support with libexif: $with_libexif, \ JPEG load/save with libjpeg: $with_jpeg, \ PNG load with libspng: $with_libspng, \ @@ -1427,7 +1427,7 @@ accelerate loops with orc: $with_orc (requires orc-0.4.11 or later) ICC profile support with lcms: $with_lcms zlib: $with_zlib -text rendering with pangoft2: $with_pangoft2 +text rendering with pangocairo: $with_pangocairo EXIF metadata support with libexif: $with_libexif ## File format support diff --git a/libvips/create/create.c b/libvips/create/create.c index 93c35796..de1da74c 100644 --- a/libvips/create/create.c +++ b/libvips/create/create.c @@ -116,9 +116,9 @@ vips_create_operation_init( void ) extern GType vips_gaussmat_get_type( void ); extern GType vips_logmat_get_type( void ); extern GType vips_gaussnoise_get_type( void ); -#ifdef HAVE_PANGOFT2 +#ifdef HAVE_PANGOCAIRO extern GType vips_text_get_type( void ); -#endif /*HAVE_PANGOFT2*/ +#endif /*HAVE_PANGOCAIRO*/ extern GType vips_xyz_get_type( void ); extern GType vips_eye_get_type( void ); extern GType vips_grey_get_type( void ); @@ -146,9 +146,9 @@ vips_create_operation_init( void ) vips_gaussmat_get_type(); vips_logmat_get_type(); vips_gaussnoise_get_type(); -#ifdef HAVE_PANGOFT2 +#ifdef HAVE_PANGOCAIRO vips_text_get_type(); -#endif /*HAVE_PANGOFT2*/ +#endif /*HAVE_PANGOCAIRO*/ vips_xyz_get_type(); vips_eye_get_type(); vips_grey_get_type(); diff --git a/libvips/create/text.c b/libvips/create/text.c index bfa6dd4d..71dd9786 100644 --- a/libvips/create/text.c +++ b/libvips/create/text.c @@ -32,6 +32,9 @@ * - fitting could occasionally terminate early [levmorozov] * 16/5/20 [keiviv] * - don't add fontfiles repeatedly + * 12/4/21 + * - switch to cairo for text rendering + * - add rgba flag */ /* @@ -74,11 +77,13 @@ #include #include +#include -#ifdef HAVE_PANGOFT2 +#ifdef HAVE_PANGOCAIRO +#include #include -#include +#include #include "pcreate.h" @@ -96,7 +101,6 @@ typedef struct _VipsText { char *fontfile; gboolean rgba; - FT_Bitmap bitmap; PangoContext *context; PangoLayout *layout; @@ -130,7 +134,6 @@ vips_text_dispose( GObject *gobject ) VIPS_UNREF( text->layout ); VIPS_UNREF( text->context ); - VIPS_FREE( text->bitmap.buffer ); G_OBJECT_CLASS( vips_text_parent_class )->dispose( gobject ); } @@ -187,8 +190,8 @@ vips_text_get_extents( VipsText *text, VipsRect *extents ) PangoRectangle ink_rect; PangoRectangle logical_rect; - pango_ft2_font_map_set_resolution( - PANGO_FT2_FONT_MAP( vips_text_fontmap ), text->dpi, text->dpi ); + pango_cairo_font_map_set_resolution( + PANGO_CAIRO_FONT_MAP( vips_text_fontmap ), text->dpi ); VIPS_UNREF( text->layout ); if( !(text->layout = text_layout_new( text->context, @@ -345,8 +348,8 @@ vips_text_build( VipsObject *object ) VipsRect extents; int bands; VipsInterpretation interpretation; - FT_Pixel_Mode pixel_mode; - int y; + cairo_surface_t *surface; + cairo_t *cr; if( VIPS_OBJECT_CLASS( vips_text_parent_class )->build( object ) ) return( -1 ); @@ -360,7 +363,7 @@ vips_text_build( VipsObject *object ) g_mutex_lock( vips_text_lock ); if( !vips_text_fontmap ) - vips_text_fontmap = pango_ft2_font_map_new(); + vips_text_fontmap = pango_cairo_font_map_new(); if( !vips_text_fontfiles ) vips_text_fontfiles = g_hash_table_new( g_str_hash, g_str_equal ); @@ -368,6 +371,7 @@ vips_text_build( VipsObject *object ) text->context = pango_font_map_create_context( PANGO_FONT_MAP( vips_text_fontmap ) ); + /* if( text->fontfile && !g_hash_table_lookup( vips_text_fontfiles, text->fontfile ) ) { if( !FcConfigAppFontAddFile( NULL, @@ -382,6 +386,7 @@ vips_text_build( VipsObject *object ) text->fontfile, g_strdup( text->fontfile ) ); } + */ /* If our caller set height and not dpi, we adjust dpi until * we get a fit. @@ -406,54 +411,56 @@ vips_text_build( VipsObject *object ) if( text->rgba ) { interpretation = VIPS_INTERPRETATION_sRGB; bands = 4; - pixel_mode = FT_PIXEL_MODE_BGRA; } else { interpretation = VIPS_INTERPRETATION_MULTIBAND; bands = 1; - pixel_mode = FT_PIXEL_MODE_GRAY; } - text->bitmap.width = extents.width; - text->bitmap.pitch = (bands * text->bitmap.width + 3) & ~3; - text->bitmap.rows = extents.height; - if( !(text->bitmap.buffer = - VIPS_ARRAY( NULL, - text->bitmap.pitch * text->bitmap.rows, VipsPel )) ) { - g_mutex_unlock( vips_text_lock ); - return( -1 ); - } - text->bitmap.num_grays = 256; - text->bitmap.pixel_mode = pixel_mode; - memset( text->bitmap.buffer, 0x00, - text->bitmap.pitch * text->bitmap.rows ); - - pango_ft2_render_layout( &text->bitmap, text->layout, - -extents.left, -extents.top ); - /* Set DPI as pixels/mm. */ vips_image_init_fields( create->out, - text->bitmap.width, text->bitmap.rows, bands, + extents.width, extents.height, bands, VIPS_FORMAT_UCHAR, VIPS_CODING_NONE, interpretation, text->dpi / 25.4, text->dpi / 25.4 ); - g_mutex_unlock( vips_text_lock ); - vips_image_pipelinev( create->out, VIPS_DEMAND_STYLE_ANY, NULL ); create->out->Xoffset = extents.left; create->out->Yoffset = extents.top; - if( text->rgba ) { - /* Convert premultiplied BGRA to RGBA. - */ + if( vips_image_write_prepare( create->out ) ) { + g_mutex_unlock( vips_text_lock ); + return( -1 ); } - for( y = 0; y < text->bitmap.rows; y++ ) - if( vips_image_write_line( create->out, y, - (VipsPel *) text->bitmap.buffer + - y * text->bitmap.pitch ) ) - return( -1 ); + surface = cairo_image_surface_create_for_data( + VIPS_IMAGE_ADDR( create->out, 0, 0 ), + text->rgba ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_A8, + create->out->Xsize, create->out->Ysize, + VIPS_IMAGE_SIZEOF_LINE( create->out ) ); + cr = cairo_create( surface ); + cairo_surface_destroy( surface ); + + cairo_translate( cr, -extents.left, -extents.top ); + + pango_cairo_show_layout( cr, text->layout ); + + cairo_destroy( cr ); + + g_mutex_unlock( vips_text_lock ); + + if( text->rgba ) { + int y; + + /* Cairo makes pre-multipled BRGA -- we must byteswap and + * unpremultiply. + */ + for( y = 0; y < create->out->Ysize; y++ ) + vips__premultiplied_bgra2rgba( + (guint32 *) + VIPS_IMAGE_ADDR( create->out, 0, y ), + create->out->Xsize ); + } return( 0 ); } @@ -569,11 +576,10 @@ vips_text_init( VipsText *text ) { text->align = VIPS_ALIGN_LOW; text->dpi = 72; - text->bitmap.buffer = NULL; VIPS_SETSTR( text->font, "sans 12" ); } -#endif /*HAVE_PANGOFT2*/ +#endif /*HAVE_PANGOCAIRO*/ /** * vips_text: From 985a3e2282e635f336f036e6206ca9238b7dfd37 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 12 Apr 2021 15:46:56 +0100 Subject: [PATCH 11/41] reenable fontfile --- libvips/create/text.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libvips/create/text.c b/libvips/create/text.c index 71dd9786..26c4d177 100644 --- a/libvips/create/text.c +++ b/libvips/create/text.c @@ -84,6 +84,7 @@ #include #include #include +#include #include "pcreate.h" @@ -371,7 +372,6 @@ vips_text_build( VipsObject *object ) text->context = pango_font_map_create_context( PANGO_FONT_MAP( vips_text_fontmap ) ); - /* if( text->fontfile && !g_hash_table_lookup( vips_text_fontfiles, text->fontfile ) ) { if( !FcConfigAppFontAddFile( NULL, @@ -386,7 +386,6 @@ vips_text_build( VipsObject *object ) text->fontfile, g_strdup( text->fontfile ) ); } - */ /* If our caller set height and not dpi, we adjust dpi until * we get a fit. From b4fe2c7b8b01994e0a59479bbc8d65f88166192e Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 12 Apr 2021 16:52:14 +0100 Subject: [PATCH 12/41] Note pagocairo dep in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0c512df1..45c05f9e 100644 --- a/README.md +++ b/README.md @@ -243,10 +243,10 @@ If you are going to be using libvips with untrusted images, perhaps in a web server, for example, you should consider the security implications of enabling a package with such a large attack surface. -### pangoft2 +### pangocairo If available, libvips adds support for text rendering. You need the -package pangoft2 in `pkg-config --list-all`. +package pangocairo in `pkg-config --list-all`. ### orc-0.4 From b27a10c54727d080e8d51f8d70e7b9c4701689eb Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 12 Apr 2021 16:55:01 +0100 Subject: [PATCH 13/41] ask brew to update/upgrade might help CI failures on macOS --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 32d8c34a..67a166f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,7 +60,9 @@ jobs: - name: Install macOS dependencies if: contains(matrix.os, 'macos') - run: + run: | + brew update + brew upgrade brew install autoconf automake libtool gtk-doc gobject-introspection From 31fe2746266b1f2da457be778b49b8b2f0a6943f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 12 Apr 2021 17:29:54 +0100 Subject: [PATCH 14/41] fix mono rendering always render RGBA, then in mono mode just use the alpha --- libvips/create/text.c | 56 ++++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/libvips/create/text.c b/libvips/create/text.c index 26c4d177..d09f5e5b 100644 --- a/libvips/create/text.c +++ b/libvips/create/text.c @@ -345,10 +345,10 @@ vips_text_build( VipsObject *object ) VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); VipsCreate *create = VIPS_CREATE( object ); VipsText *text = (VipsText *) object; + VipsImage **t = (VipsImage **) vips_object_local_array( object, 3 ); VipsRect extents; - int bands; - VipsInterpretation interpretation; + VipsImage *image; cairo_surface_t *surface; cairo_t *cr; @@ -407,36 +407,29 @@ vips_text_build( VipsObject *object ) return( -1 ); } - if( text->rgba ) { - interpretation = VIPS_INTERPRETATION_sRGB; - bands = 4; - } - else { - interpretation = VIPS_INTERPRETATION_MULTIBAND; - bands = 1; - } - /* Set DPI as pixels/mm. */ - vips_image_init_fields( create->out, - extents.width, extents.height, bands, + image = t[0] = vips_image_new_memory(); + vips_image_init_fields( image, + extents.width, extents.height, 4, VIPS_FORMAT_UCHAR, VIPS_CODING_NONE, - interpretation, text->dpi / 25.4, text->dpi / 25.4 ); + VIPS_INTERPRETATION_sRGB, + text->dpi / 25.4, text->dpi / 25.4 ); - vips_image_pipelinev( create->out, VIPS_DEMAND_STYLE_ANY, NULL ); - create->out->Xoffset = extents.left; - create->out->Yoffset = extents.top; + vips_image_pipelinev( image, VIPS_DEMAND_STYLE_ANY, NULL ); + image->Xoffset = extents.left; + image->Yoffset = extents.top; - if( vips_image_write_prepare( create->out ) ) { + if( vips_image_write_prepare( image ) ) { g_mutex_unlock( vips_text_lock ); return( -1 ); } surface = cairo_image_surface_create_for_data( - VIPS_IMAGE_ADDR( create->out, 0, 0 ), - text->rgba ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_A8, - create->out->Xsize, create->out->Ysize, - VIPS_IMAGE_SIZEOF_LINE( create->out ) ); + VIPS_IMAGE_ADDR( image, 0, 0 ), + CAIRO_FORMAT_ARGB32, + image->Xsize, image->Ysize, + VIPS_IMAGE_SIZEOF_LINE( image ) ); cr = cairo_create( surface ); cairo_surface_destroy( surface ); @@ -454,12 +447,25 @@ vips_text_build( VipsObject *object ) /* Cairo makes pre-multipled BRGA -- we must byteswap and * unpremultiply. */ - for( y = 0; y < create->out->Ysize; y++ ) + for( y = 0; y < image->Ysize; y++ ) vips__premultiplied_bgra2rgba( (guint32 *) - VIPS_IMAGE_ADDR( create->out, 0, y ), - create->out->Xsize ); + VIPS_IMAGE_ADDR( image, 0, y ), + image->Xsize ); } + else { + /* We just want the alpha channel. + */ + if( vips_extract_band( image, &t[1], 3, NULL ) || + vips_copy( t[1], &t[2], + "interpretation", VIPS_INTERPRETATION_MULTIBAND, + NULL ) ) + return( -1 ); + image = t[2]; + } + + if( vips_image_write( image, create->out ) ) + return( -1 ); return( 0 ); } From cc04449dc5d455964809c16d5df088568728eaa0 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 12 Apr 2021 20:40:00 +0100 Subject: [PATCH 15/41] remove extraeous "brew upgrade" --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 67a166f7..c038210e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,7 +62,6 @@ jobs: if: contains(matrix.os, 'macos') run: | brew update - brew upgrade brew install autoconf automake libtool gtk-doc gobject-introspection From 21c565d5d620d7b032066901bcb1b11f6424e504 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 14 Apr 2021 11:32:08 +0100 Subject: [PATCH 16/41] improve comment --- libvips/foreign/nsgifload.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libvips/foreign/nsgifload.c b/libvips/foreign/nsgifload.c index 99c9234b..06c5215a 100644 --- a/libvips/foreign/nsgifload.c +++ b/libvips/foreign/nsgifload.c @@ -534,7 +534,8 @@ vips_foreign_load_nsgif_class_init( VipsForeignLoadNsgifClass *class ) static void * vips_foreign_load_nsgif_bitmap_create( int width, int height ) { - /* Enforce max GIF dimensions of 16383 (0x7FFF). + /* Enforce max GIF dimensions of 16383 (0x7FFF). This should be enough + * for anyone, and will prevent the worst GIF bombs. */ if( width <= 0 || width > 16383 || @@ -544,6 +545,7 @@ vips_foreign_load_nsgif_bitmap_create( int width, int height ) "%s", _( "bad image dimensions") ); return( NULL ); } + return g_malloc0( (gsize) width * height * 4 ); } From f66e20e017c2b51d61a7782753b41794460dc1c9 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Wed, 31 Mar 2021 19:04:02 +0100 Subject: [PATCH 17/41] lzw: Simplify new code handling. The tiny overhead of an extra time through the output loop is worth the simpler code. --- libvips/foreign/libnsgif/lzw.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/libvips/foreign/libnsgif/lzw.c b/libvips/foreign/libnsgif/lzw.c index 1f854964..16e1a727 100644 --- a/libvips/foreign/libnsgif/lzw.c +++ b/libvips/foreign/libnsgif/lzw.c @@ -4,6 +4,7 @@ * http://www.opensource.org/licenses/mit-license.php * * Copyright 2017 Michael Drake + * Copyright 2021 Michael Drake */ #include @@ -307,7 +308,6 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, { lzw_result res; uint32_t code_new; - uint32_t code_out; uint8_t last_value; uint8_t *stack_pos = ctx->stack_base; uint32_t clear_code = ctx->clear_code; @@ -335,12 +335,9 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, } else if (code_new < current_entry) { /* Code is in table */ - code_out = code_new; last_value = table[code_new].first_value; } else { /* Code not in table */ - *stack_pos++ = ctx->previous_code_first; - code_out = ctx->previous_code; last_value = ctx->previous_code_first; } @@ -366,15 +363,13 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, ctx->previous_code_first = table[code_new].first_value; ctx->previous_code = code_new; - /* Put rest of data for this code on output stack. - * Note, in the case of "code not in table", the last entry of the - * current code has already been placed on the stack above. */ - while (code_out > clear_code) { - struct lzw_dictionary_entry *entry = table + code_out; + /* Put data for this code on output stack. */ + while (code_new > clear_code) { + struct lzw_dictionary_entry *entry = table + code_new; *stack_pos++ = entry->last_value; - code_out = entry->previous_entry; + code_new = entry->previous_entry; } - *stack_pos++ = table[code_out].last_value; + *stack_pos++ = table[code_new].last_value; *stack_pos_out = stack_pos; return LZW_OK; From 47a441f86bb774f5c4d06b3fa3ee5742dd973a34 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Wed, 31 Mar 2021 19:12:38 +0100 Subject: [PATCH 18/41] lzw: Create #define for number of dictionary entry slots. --- libvips/foreign/libnsgif/lzw.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libvips/foreign/libnsgif/lzw.c b/libvips/foreign/libnsgif/lzw.c index 16e1a727..e7b06f1c 100644 --- a/libvips/foreign/libnsgif/lzw.c +++ b/libvips/foreign/libnsgif/lzw.c @@ -21,6 +21,8 @@ * Decoder for GIF LZW data. */ +/** Maximum number of dictionary entries. */ +#define LZW_TABLE_ENTRY_MAX (1u << LZW_CODE_MAX) /** * Context for reading LZW data. @@ -79,10 +81,10 @@ struct lzw_ctx { uint32_t current_entry; /**< Next position in table to fill. */ /** Output value stack. */ - uint8_t stack_base[1 << LZW_CODE_MAX]; + uint8_t stack_base[LZW_TABLE_ENTRY_MAX]; /** LZW decode dictionary. Generated during decode. */ - struct lzw_dictionary_entry table[1 << LZW_CODE_MAX]; + struct lzw_dictionary_entry table[LZW_TABLE_ENTRY_MAX]; }; @@ -342,7 +344,7 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, } /* Add to the dictionary, only if there's space */ - if (current_entry < (1 << LZW_CODE_MAX)) { + if (current_entry < LZW_TABLE_ENTRY_MAX) { struct lzw_dictionary_entry *entry = table + current_entry; entry->last_value = last_value; entry->first_value = ctx->previous_code_first; From 1714cf6c3a5652a91fa59be280ada539718c1790 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Wed, 31 Mar 2021 19:40:35 +0100 Subject: [PATCH 19/41] lzw: Split out dictionary augmentation. --- libvips/foreign/libnsgif/lzw.c | 35 ++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/libvips/foreign/libnsgif/lzw.c b/libvips/foreign/libnsgif/lzw.c index e7b06f1c..3504982c 100644 --- a/libvips/foreign/libnsgif/lzw.c +++ b/libvips/foreign/libnsgif/lzw.c @@ -303,6 +303,24 @@ lzw_result lzw_decode_init( return lzw__clear_codes(ctx, stack_pos_out); } +/** + * Create new dictionary entry. + * + * \param[in] ctx LZW reading context, updated. + * \param[in] code Last value code for new dictionary entry. + */ +static inline void lzw__dictionary_add_entry( + struct lzw_ctx *ctx, + uint32_t code) +{ + struct lzw_dictionary_entry *entry = &ctx->table[ctx->current_entry]; + + entry->last_value = code; + entry->first_value = ctx->previous_code_first; + entry->previous_entry = ctx->previous_code; + + ctx->current_entry++; +} /* Exported function, documented in lzw.h */ lzw_result lzw_decode(struct lzw_ctx *ctx, @@ -310,7 +328,6 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, { lzw_result res; uint32_t code_new; - uint8_t last_value; uint8_t *stack_pos = ctx->stack_base; uint32_t clear_code = ctx->clear_code; uint32_t current_entry = ctx->current_entry; @@ -334,22 +351,12 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, } else if (code_new > current_entry) { /* Code is invalid */ return LZW_BAD_CODE; - - } else if (code_new < current_entry) { - /* Code is in table */ - last_value = table[code_new].first_value; - } else { - /* Code not in table */ - last_value = ctx->previous_code_first; } - /* Add to the dictionary, only if there's space */ if (current_entry < LZW_TABLE_ENTRY_MAX) { - struct lzw_dictionary_entry *entry = table + current_entry; - entry->last_value = last_value; - entry->first_value = ctx->previous_code_first; - entry->previous_entry = ctx->previous_code; - ctx->current_entry++; + lzw__dictionary_add_entry(ctx, (code_new < current_entry) ? + table[code_new].first_value : + ctx->previous_code_first); } /* Ensure code size is increased, if needed. */ From eb07e204e7e58f68bcd807d67a3016774d13de74 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Wed, 31 Mar 2021 20:18:43 +0100 Subject: [PATCH 20/41] lzw: Avoid code size increment check when dictionary is full. --- libvips/foreign/libnsgif/lzw.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/libvips/foreign/libnsgif/lzw.c b/libvips/foreign/libnsgif/lzw.c index 3504982c..ed92e361 100644 --- a/libvips/foreign/libnsgif/lzw.c +++ b/libvips/foreign/libnsgif/lzw.c @@ -357,11 +357,10 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, lzw__dictionary_add_entry(ctx, (code_new < current_entry) ? table[code_new].first_value : ctx->previous_code_first); - } - /* Ensure code size is increased, if needed. */ - if (current_entry == ctx->current_code_size_max) { - if (ctx->current_code_size < LZW_CODE_MAX) { + /* Ensure code size is increased, if needed. */ + if (current_entry == ctx->current_code_size_max && + ctx->current_code_size < LZW_CODE_MAX) { ctx->current_code_size++; ctx->current_code_size_max = (1 << ctx->current_code_size) - 1; From ac2d57c288b1087d055be25c5067233e124d545d Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Wed, 31 Mar 2021 21:01:54 +0100 Subject: [PATCH 21/41] lzw: Slight simplification of clear code handling. --- libvips/foreign/libnsgif/lzw.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libvips/foreign/libnsgif/lzw.c b/libvips/foreign/libnsgif/lzw.c index ed92e361..a5fe8110 100644 --- a/libvips/foreign/libnsgif/lzw.c +++ b/libvips/foreign/libnsgif/lzw.c @@ -232,9 +232,9 @@ static lzw_result lzw__clear_codes( uint8_t *stack_pos; /* Reset dictionary building context */ - ctx->current_code_size = ctx->initial_code_size + 1; - ctx->current_code_size_max = (1 << ctx->current_code_size) - 1; - ctx->current_entry = (1 << ctx->initial_code_size) + 2; + ctx->current_code_size = ctx->initial_code_size; + ctx->current_code_size_max = (1 << ctx->initial_code_size) - 1; + ctx->current_entry = ctx->eoi_code + 1; /* There might be a sequence of clear codes, so process them all */ do { @@ -288,7 +288,7 @@ lzw_result lzw_decode_init( ctx->input.sb_bit_count = 0; /* Initialise the dictionary building context */ - ctx->initial_code_size = code_size; + ctx->initial_code_size = code_size + 1; ctx->clear_code = (1 << code_size) + 0; ctx->eoi_code = (1 << code_size) + 1; From 92d53337d885c118564de5d379d643dc57047274 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Wed, 31 Mar 2021 21:17:58 +0100 Subject: [PATCH 22/41] lzw: Rename minimum_code_size to match what it's called in spec. --- libvips/foreign/libnsgif/lzw.c | 10 +++++----- libvips/foreign/libnsgif/lzw.h | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libvips/foreign/libnsgif/lzw.c b/libvips/foreign/libnsgif/lzw.c index a5fe8110..a5f6e98e 100644 --- a/libvips/foreign/libnsgif/lzw.c +++ b/libvips/foreign/libnsgif/lzw.c @@ -269,13 +269,13 @@ lzw_result lzw_decode_init( const uint8_t *compressed_data, uint32_t compressed_data_len, uint32_t compressed_data_pos, - uint8_t code_size, + uint8_t minimum_code_size, const uint8_t ** const stack_base_out, const uint8_t ** const stack_pos_out) { struct lzw_dictionary_entry *table = ctx->table; - if (code_size >= LZW_CODE_MAX) { + if (minimum_code_size >= LZW_CODE_MAX) { return LZW_BAD_ICODE; } @@ -288,10 +288,10 @@ lzw_result lzw_decode_init( ctx->input.sb_bit_count = 0; /* Initialise the dictionary building context */ - ctx->initial_code_size = code_size + 1; + ctx->initial_code_size = minimum_code_size + 1; - ctx->clear_code = (1 << code_size) + 0; - ctx->eoi_code = (1 << code_size) + 1; + ctx->clear_code = (1 << minimum_code_size) + 0; + ctx->eoi_code = (1 << minimum_code_size) + 1; /* Initialise the standard dictionary entries */ for (uint32_t i = 0; i < ctx->clear_code; ++i) { diff --git a/libvips/foreign/libnsgif/lzw.h b/libvips/foreign/libnsgif/lzw.h index 385b4255..888526e7 100644 --- a/libvips/foreign/libnsgif/lzw.h +++ b/libvips/foreign/libnsgif/lzw.h @@ -65,7 +65,7 @@ void lzw_context_destroy( * \param[in] compressed_data_len Byte length of compressed data. * \param[in] compressed_data_pos Start position in data. Must be position * of a size byte at sub-block start. - * \param[in] code_size The initial LZW code size to use. + * \param[in] minimum_code_size The LZW Minimum Code Size. * \param[out] stack_base_out Returns base of decompressed data stack. * \param[out] stack_pos_out Returns current stack position. * There are `stack_pos_out - stack_base_out` @@ -77,7 +77,7 @@ lzw_result lzw_decode_init( const uint8_t *compressed_data, uint32_t compressed_data_len, uint32_t compressed_data_pos, - uint8_t code_size, + uint8_t minimum_code_size, const uint8_t ** const stack_base_out, const uint8_t ** const stack_pos_out); From 26c83999cc45b978b4f15b0b3649966f49933413 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Thu, 1 Apr 2021 20:40:54 +0100 Subject: [PATCH 23/41] lzw: Split out output writing. --- libvips/foreign/libnsgif/lzw.c | 47 +++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/libvips/foreign/libnsgif/lzw.c b/libvips/foreign/libnsgif/lzw.c index a5f6e98e..5b7227ff 100644 --- a/libvips/foreign/libnsgif/lzw.c +++ b/libvips/foreign/libnsgif/lzw.c @@ -322,16 +322,41 @@ static inline void lzw__dictionary_add_entry( ctx->current_entry++; } +/** + * Write values for this code to the output stack. + * + * \param[in] ctx LZW reading context, updated. + * \param[in] code LZW code to output values for. + * \param[out] stack_pos_out Returns current stack position. + * There are `stack_pos_out - ctx->stack_base` + * current stack entries. + */ +static inline void lzw__write_pixels(struct lzw_ctx *ctx, + uint32_t code, + const uint8_t ** const stack_pos_out) +{ + uint8_t *stack_pos = ctx->stack_base; + uint32_t clear_code = ctx->clear_code; + struct lzw_dictionary_entry * const table = ctx->table; + + while (code > clear_code) { + struct lzw_dictionary_entry *entry = table + code; + *stack_pos++ = entry->last_value; + code = entry->previous_entry; + } + *stack_pos++ = table[code].last_value; + + *stack_pos_out = stack_pos; + return; +} + /* Exported function, documented in lzw.h */ lzw_result lzw_decode(struct lzw_ctx *ctx, const uint8_t ** const stack_pos_out) { lzw_result res; uint32_t code_new; - uint8_t *stack_pos = ctx->stack_base; - uint32_t clear_code = ctx->clear_code; uint32_t current_entry = ctx->current_entry; - struct lzw_dictionary_entry * const table = ctx->table; /* Get a new code from the input */ res = lzw__next_code(&ctx->input, ctx->current_code_size, &code_new); @@ -340,7 +365,7 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, } /* Handle the new code */ - if (code_new == clear_code) { + if (code_new == ctx->clear_code) { /* Got Clear code */ return lzw__clear_codes(ctx, stack_pos_out); @@ -355,7 +380,7 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, if (current_entry < LZW_TABLE_ENTRY_MAX) { lzw__dictionary_add_entry(ctx, (code_new < current_entry) ? - table[code_new].first_value : + ctx->table[code_new].first_value : ctx->previous_code_first); /* Ensure code size is increased, if needed. */ @@ -368,17 +393,9 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, } /* Store details of this code as "previous code" to the context. */ - ctx->previous_code_first = table[code_new].first_value; + ctx->previous_code_first = ctx->table[code_new].first_value; ctx->previous_code = code_new; - /* Put data for this code on output stack. */ - while (code_new > clear_code) { - struct lzw_dictionary_entry *entry = table + code_new; - *stack_pos++ = entry->last_value; - code_new = entry->previous_entry; - } - *stack_pos++ = table[code_new].last_value; - - *stack_pos_out = stack_pos; + lzw__write_pixels(ctx, code_new, stack_pos_out); return LZW_OK; } From c9a639d2bda9e5ae232106acb1ff7be1c09f176a Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Thu, 1 Apr 2021 21:06:59 +0100 Subject: [PATCH 24/41] lzw: Rename a bunch of structures, functions and variables. new_code -> code last_value -> value first_value -> first previous_entry -> extends current_entry -> table_size previous_code -> prev_code previous_code_first -> prev_code_first current_code_size -> code_size current_code_size_max -> code_max lzw__next_code -> lzw_read_code --- libvips/foreign/libnsgif/lzw.c | 132 ++++++++++++++++----------------- 1 file changed, 65 insertions(+), 67 deletions(-) diff --git a/libvips/foreign/libnsgif/lzw.c b/libvips/foreign/libnsgif/lzw.c index 5b7227ff..916bcec4 100644 --- a/libvips/foreign/libnsgif/lzw.c +++ b/libvips/foreign/libnsgif/lzw.c @@ -21,7 +21,7 @@ * Decoder for GIF LZW data. */ -/** Maximum number of dictionary entries. */ +/** Maximum number of lzw table entries. */ #define LZW_TABLE_ENTRY_MAX (1u << LZW_CODE_MAX) /** @@ -46,45 +46,45 @@ struct lzw_read_ctx { }; /** - * LZW dictionary entry. + * LZW table entry. * - * Records in the dictionary are composed of 1 or more entries. - * Entries point to previous entries which can be followed to compose + * Records in the table are composed of 1 or more entries. + * Entries refer to the entry they extend which can be followed to compose * the complete record. To compose the record in reverse order, take - * the `last_value` from each entry, and move to the previous entry. - * If the previous_entry's index is < the current clear_code, then it + * the `value` from each entry, and move to the entry it extends. + * If the extended entries index is < the current clear_code, then it * is the last entry in the record. */ -struct lzw_dictionary_entry { - uint8_t last_value; /**< Last value for record ending at entry. */ - uint8_t first_value; /**< First value for entry's record. */ - uint16_t previous_entry; /**< Offset in dictionary to previous entry. */ +struct lzw_table_entry { + uint8_t value; /**< Last value for record ending at entry. */ + uint8_t first; /**< First value in entry's entire record. */ + uint16_t extends; /**< Offset in table to previous entry. */ }; /** * LZW decompression context. */ struct lzw_ctx { - /** Input reading context */ - struct lzw_read_ctx input; + struct lzw_read_ctx input; /**< Input reading context */ - uint32_t previous_code; /**< Code read from input previously. */ - uint32_t previous_code_first; /**< First value of previous code. */ + uint32_t prev_code; /**< Code read from input previously. */ + uint32_t prev_code_first; /**< First value of previous code. */ - uint32_t initial_code_size; /**< Starting LZW code size. */ - uint32_t current_code_size; /**< Current LZW code size. */ - uint32_t current_code_size_max; /**< Max code value for current size. */ + uint32_t initial_code_size; /**< Starting LZW code size. */ + + uint32_t code_size; /**< Current LZW code size. */ + uint32_t code_max; /**< Max code value for current code size. */ uint32_t clear_code; /**< Special Clear code value */ uint32_t eoi_code; /**< Special End of Information code value */ - uint32_t current_entry; /**< Next position in table to fill. */ + uint32_t table_size; /**< Next position in table to fill. */ /** Output value stack. */ uint8_t stack_base[LZW_TABLE_ENTRY_MAX]; - /** LZW decode dictionary. Generated during decode. */ - struct lzw_dictionary_entry table[LZW_TABLE_ENTRY_MAX]; + /** LZW code table. Generated during decode. */ + struct lzw_table_entry table[LZW_TABLE_ENTRY_MAX]; }; @@ -155,7 +155,7 @@ static lzw_result lzw__block_advance(struct lzw_read_ctx *ctx) * \param[out] code_out Returns an LZW code on success. * \return LZW_OK or LZW_OK_EOD on success, appropriate error otherwise. */ -static inline lzw_result lzw__next_code( +static inline lzw_result lzw__read_code( struct lzw_read_ctx *ctx, uint8_t code_size, uint32_t *code_out) @@ -218,7 +218,7 @@ static inline lzw_result lzw__next_code( /** - * Clear LZW code dictionary. + * Clear LZW code table. * * \param[in] ctx LZW reading context, updated. * \param[out] stack_pos_out Returns current stack position. @@ -231,28 +231,28 @@ static lzw_result lzw__clear_codes( uint32_t code; uint8_t *stack_pos; - /* Reset dictionary building context */ - ctx->current_code_size = ctx->initial_code_size; - ctx->current_code_size_max = (1 << ctx->initial_code_size) - 1; - ctx->current_entry = ctx->eoi_code + 1; + /* Reset table building context */ + ctx->code_size = ctx->initial_code_size; + ctx->code_max = (1 << ctx->initial_code_size) - 1; + ctx->table_size = ctx->eoi_code + 1; /* There might be a sequence of clear codes, so process them all */ do { - lzw_result res = lzw__next_code(&ctx->input, - ctx->current_code_size, &code); + lzw_result res = lzw__read_code(&ctx->input, + ctx->code_size, &code); if (res != LZW_OK) { return res; } } while (code == ctx->clear_code); - /* The initial code must be from the initial dictionary. */ + /* The initial code must be from the initial table. */ if (code > ctx->clear_code) { return LZW_BAD_ICODE; } /* Record this initial code as "previous" code, needed during decode. */ - ctx->previous_code = code; - ctx->previous_code_first = code; + ctx->prev_code = code; + ctx->prev_code_first = code; /* Reset the stack, and add first non-clear code added as first item. */ stack_pos = ctx->stack_base; @@ -273,7 +273,7 @@ lzw_result lzw_decode_init( const uint8_t ** const stack_base_out, const uint8_t ** const stack_pos_out) { - struct lzw_dictionary_entry *table = ctx->table; + struct lzw_table_entry *table = ctx->table; if (minimum_code_size >= LZW_CODE_MAX) { return LZW_BAD_ICODE; @@ -287,16 +287,16 @@ lzw_result lzw_decode_init( ctx->input.sb_bit = 0; ctx->input.sb_bit_count = 0; - /* Initialise the dictionary building context */ + /* Initialise the table building context */ ctx->initial_code_size = minimum_code_size + 1; ctx->clear_code = (1 << minimum_code_size) + 0; ctx->eoi_code = (1 << minimum_code_size) + 1; - /* Initialise the standard dictionary entries */ + /* Initialise the standard table entries */ for (uint32_t i = 0; i < ctx->clear_code; ++i) { - table[i].first_value = i; - table[i].last_value = i; + table[i].first = i; + table[i].value = i; } *stack_base_out = ctx->stack_base; @@ -304,22 +304,22 @@ lzw_result lzw_decode_init( } /** - * Create new dictionary entry. + * Create new table entry. * * \param[in] ctx LZW reading context, updated. - * \param[in] code Last value code for new dictionary entry. + * \param[in] code Last value code for new table entry. */ -static inline void lzw__dictionary_add_entry( +static inline void lzw__table_add_entry( struct lzw_ctx *ctx, uint32_t code) { - struct lzw_dictionary_entry *entry = &ctx->table[ctx->current_entry]; + struct lzw_table_entry *entry = &ctx->table[ctx->table_size]; - entry->last_value = code; - entry->first_value = ctx->previous_code_first; - entry->previous_entry = ctx->previous_code; + entry->value = code; + entry->first = ctx->prev_code_first; + entry->extends = ctx->prev_code; - ctx->current_entry++; + ctx->table_size++; } /** @@ -337,14 +337,14 @@ static inline void lzw__write_pixels(struct lzw_ctx *ctx, { uint8_t *stack_pos = ctx->stack_base; uint32_t clear_code = ctx->clear_code; - struct lzw_dictionary_entry * const table = ctx->table; + struct lzw_table_entry * const table = ctx->table; while (code > clear_code) { - struct lzw_dictionary_entry *entry = table + code; - *stack_pos++ = entry->last_value; - code = entry->previous_entry; + struct lzw_table_entry *entry = table + code; + *stack_pos++ = entry->value; + code = entry->extends; } - *stack_pos++ = table[code].last_value; + *stack_pos++ = table[code].value; *stack_pos_out = stack_pos; return; @@ -355,47 +355,45 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, const uint8_t ** const stack_pos_out) { lzw_result res; - uint32_t code_new; - uint32_t current_entry = ctx->current_entry; + uint32_t code; /* Get a new code from the input */ - res = lzw__next_code(&ctx->input, ctx->current_code_size, &code_new); + res = lzw__read_code(&ctx->input, ctx->code_size, &code); if (res != LZW_OK) { return res; } /* Handle the new code */ - if (code_new == ctx->clear_code) { + if (code == ctx->clear_code) { /* Got Clear code */ return lzw__clear_codes(ctx, stack_pos_out); - } else if (code_new == ctx->eoi_code) { + } else if (code == ctx->eoi_code) { /* Got End of Information code */ return LZW_EOI_CODE; - } else if (code_new > current_entry) { + } else if (code > ctx->table_size) { /* Code is invalid */ return LZW_BAD_CODE; } - if (current_entry < LZW_TABLE_ENTRY_MAX) { - lzw__dictionary_add_entry(ctx, (code_new < current_entry) ? - ctx->table[code_new].first_value : - ctx->previous_code_first); + if (ctx->table_size < LZW_TABLE_ENTRY_MAX) { + uint32_t size = ctx->table_size; + lzw__table_add_entry(ctx, (code < size) ? + ctx->table[code].first : + ctx->prev_code_first); /* Ensure code size is increased, if needed. */ - if (current_entry == ctx->current_code_size_max && - ctx->current_code_size < LZW_CODE_MAX) { - ctx->current_code_size++; - ctx->current_code_size_max = - (1 << ctx->current_code_size) - 1; + if (size == ctx->code_max && ctx->code_size < LZW_CODE_MAX) { + ctx->code_size++; + ctx->code_max = (1 << ctx->code_size) - 1; } } /* Store details of this code as "previous code" to the context. */ - ctx->previous_code_first = ctx->table[code_new].first_value; - ctx->previous_code = code_new; + ctx->prev_code_first = ctx->table[code].first; + ctx->prev_code = code; - lzw__write_pixels(ctx, code_new, stack_pos_out); + lzw__write_pixels(ctx, code, stack_pos_out); return LZW_OK; } From 8d8aa0dd463096bf4ea5326eb06a839e21458846 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Fri, 2 Apr 2021 12:34:35 +0100 Subject: [PATCH 25/41] gif: Split out gif frame data decode. --- libvips/foreign/libnsgif/libnsgif.c | 141 ++++++++++++++++------------ 1 file changed, 82 insertions(+), 59 deletions(-) diff --git a/libvips/foreign/libnsgif/libnsgif.c b/libvips/foreign/libnsgif/libnsgif.c index 1c4bd00e..2808113e 100644 --- a/libvips/foreign/libnsgif/libnsgif.c +++ b/libvips/foreign/libnsgif/libnsgif.c @@ -618,6 +618,83 @@ static gif_result gif__recover_previous_frame(const gif_animation *gif) return GIF_OK; } +static inline gif_result +gif__decode(gif_animation *gif, + unsigned int frame, + unsigned int width, + unsigned int height, + unsigned int offset_x, + unsigned int offset_y, + unsigned int interlace, + uint8_t minimum_code_size, + unsigned int *restrict frame_data, + unsigned int *restrict colour_table) +{ + const uint8_t *stack_base; + const uint8_t *stack_pos; + gif_result ret = GIF_OK; + lzw_result res; + + /* Initialise the LZW decoding */ + res = lzw_decode_init(gif->lzw_ctx, gif->gif_data, + gif->buffer_size, gif->buffer_position, + minimum_code_size, &stack_base, &stack_pos); + if (res != LZW_OK) { + return gif_error_from_lzw(res); + } + + for (unsigned int y = 0; y < height; y++) { + unsigned int x; + unsigned int decode_y; + unsigned int *frame_scanline; + + if (interlace) { + decode_y = gif_interlaced_line(height, y) + offset_y; + } else { + decode_y = y + offset_y; + } + frame_scanline = frame_data + offset_x + (decode_y * gif->width); + + /* Rather than decoding pixel by pixel, we try to burst + * out streams of data to remove the need for end-of + * data checks every pixel. + */ + x = width; + while (x > 0) { + unsigned int burst_bytes = (stack_pos - stack_base); + if (burst_bytes > 0) { + if (burst_bytes > x) { + burst_bytes = x; + } + x -= burst_bytes; + while (burst_bytes-- > 0) { + register unsigned char colour; + colour = *--stack_pos; + if (((gif->frames[frame].transparency) && + (colour != gif->frames[frame].transparency_index)) || + (!gif->frames[frame].transparency)) { + *frame_scanline = colour_table[colour]; + } + frame_scanline++; + } + } else { + res = lzw_decode(gif->lzw_ctx, &stack_pos); + if (res != LZW_OK) { + /* Unexpected end of frame, try to recover */ + if (res == LZW_OK_EOD) { + ret = GIF_OK; + } else { + ret = gif_error_from_lzw(res); + } + break; + } + } + } + } + + return ret; +} + /** * decode a gif frame * @@ -638,11 +715,8 @@ gif_internal_decode_frame(gif_animation *gif, unsigned int flags, colour_table_size, interlace; unsigned int *colour_table; unsigned int *frame_data = 0; // Set to 0 for no warnings - unsigned int *frame_scanline; unsigned int save_buffer_position; unsigned int return_value = 0; - unsigned int x, y, decode_y, burst_bytes; - register unsigned char colour; /* Ensure this frame is supposed to be decoded */ if (gif->frames[frame].display == false) { @@ -796,10 +870,6 @@ gif_internal_decode_frame(gif_animation *gif, /* If we are clearing the image we just clear, if not decode */ if (!clear_image) { - lzw_result res; - const uint8_t *stack_base; - const uint8_t *stack_pos; - /* Ensure we have enough data for a 1-byte LZW code size + * 1-byte gif trailer */ @@ -863,62 +933,15 @@ gif_internal_decode_frame(gif_animation *gif, gif->decoded_frame = frame; gif->buffer_position = (gif_data - gif->gif_data) + 1; - /* Initialise the LZW decoding */ - res = lzw_decode_init(gif->lzw_ctx, gif->gif_data, - gif->buffer_size, gif->buffer_position, - gif_data[0], &stack_base, &stack_pos); - if (res != LZW_OK) { - return gif_error_from_lzw(res); - } - - /* Decompress the data */ - for (y = 0; y < height; y++) { - if (interlace) { - decode_y = gif_interlaced_line(height, y) + offset_y; - } else { - decode_y = y + offset_y; - } - frame_scanline = frame_data + offset_x + (decode_y * gif->width); - - /* Rather than decoding pixel by pixel, we try to burst - * out streams of data to remove the need for end-of - * data checks every pixel. - */ - x = width; - while (x > 0) { - burst_bytes = (stack_pos - stack_base); - if (burst_bytes > 0) { - if (burst_bytes > x) { - burst_bytes = x; - } - x -= burst_bytes; - while (burst_bytes-- > 0) { - colour = *--stack_pos; - if (((gif->frames[frame].transparency) && - (colour != gif->frames[frame].transparency_index)) || - (!gif->frames[frame].transparency)) { - *frame_scanline = colour_table[colour]; - } - frame_scanline++; - } - } else { - res = lzw_decode(gif->lzw_ctx, &stack_pos); - if (res != LZW_OK) { - /* Unexpected end of frame, try to recover */ - if (res == LZW_OK_EOD) { - return_value = GIF_OK; - } else { - return_value = gif_error_from_lzw(res); - } - goto gif_decode_frame_exit; - } - } - } - } + return_value = gif__decode(gif, frame, width, height, + offset_x, offset_y, interlace, gif_data[0], + frame_data, colour_table); } else { /* Clear our frame */ if (gif->frames[frame].disposal_method == GIF_FRAME_CLEAR) { + unsigned int y; for (y = 0; y < height; y++) { + unsigned int *frame_scanline; frame_scanline = frame_data + offset_x + ((offset_y + y) * gif->width); if (gif->frames[frame].transparency) { memset(frame_scanline, From 06443e64e95ad757a868edf12e524ced8458f9af Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Fri, 2 Apr 2021 22:19:42 +0100 Subject: [PATCH 26/41] lzw: Store code's value count in table. --- libvips/foreign/libnsgif/lzw.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libvips/foreign/libnsgif/lzw.c b/libvips/foreign/libnsgif/lzw.c index 916bcec4..7032501a 100644 --- a/libvips/foreign/libnsgif/lzw.c +++ b/libvips/foreign/libnsgif/lzw.c @@ -58,6 +58,7 @@ struct lzw_read_ctx { struct lzw_table_entry { uint8_t value; /**< Last value for record ending at entry. */ uint8_t first; /**< First value in entry's entire record. */ + uint16_t count; /**< Count of values in this entry's record. */ uint16_t extends; /**< Offset in table to previous entry. */ }; @@ -69,6 +70,7 @@ struct lzw_ctx { uint32_t prev_code; /**< Code read from input previously. */ uint32_t prev_code_first; /**< First value of previous code. */ + uint32_t prev_code_count; /**< Total values for previous code. */ uint32_t initial_code_size; /**< Starting LZW code size. */ @@ -253,6 +255,7 @@ static lzw_result lzw__clear_codes( /* Record this initial code as "previous" code, needed during decode. */ ctx->prev_code = code; ctx->prev_code_first = code; + ctx->prev_code_count = 1; /* Reset the stack, and add first non-clear code added as first item. */ stack_pos = ctx->stack_base; @@ -297,6 +300,7 @@ lzw_result lzw_decode_init( for (uint32_t i = 0; i < ctx->clear_code; ++i) { table[i].first = i; table[i].value = i; + table[i].count = 1; } *stack_base_out = ctx->stack_base; @@ -317,6 +321,7 @@ static inline void lzw__table_add_entry( entry->value = code; entry->first = ctx->prev_code_first; + entry->count = ctx->prev_code_count + 1; entry->extends = ctx->prev_code; ctx->table_size++; @@ -392,6 +397,7 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, /* Store details of this code as "previous code" to the context. */ ctx->prev_code_first = ctx->table[code].first; + ctx->prev_code_count = ctx->table[code].count; ctx->prev_code = code; lzw__write_pixels(ctx, code, stack_pos_out); From 367f64c5bc74f7d3f77d8bde3883f4a5b54103e5 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Sat, 3 Apr 2021 17:35:55 +0100 Subject: [PATCH 27/41] lzw: Output values in picture order. --- libvips/foreign/libnsgif/libnsgif.c | 19 ++++++----- libvips/foreign/libnsgif/lzw.c | 50 +++++++++++++++-------------- libvips/foreign/libnsgif/lzw.h | 21 ++++-------- 3 files changed, 41 insertions(+), 49 deletions(-) diff --git a/libvips/foreign/libnsgif/libnsgif.c b/libvips/foreign/libnsgif/libnsgif.c index 2808113e..52a83e2f 100644 --- a/libvips/foreign/libnsgif/libnsgif.c +++ b/libvips/foreign/libnsgif/libnsgif.c @@ -632,17 +632,19 @@ gif__decode(gif_animation *gif, { const uint8_t *stack_base; const uint8_t *stack_pos; + uint32_t written = 0; gif_result ret = GIF_OK; lzw_result res; /* Initialise the LZW decoding */ res = lzw_decode_init(gif->lzw_ctx, gif->gif_data, gif->buffer_size, gif->buffer_position, - minimum_code_size, &stack_base, &stack_pos); + minimum_code_size, &stack_base); if (res != LZW_OK) { return gif_error_from_lzw(res); } + stack_pos = stack_base; for (unsigned int y = 0; y < height; y++) { unsigned int x; unsigned int decode_y; @@ -655,21 +657,18 @@ gif__decode(gif_animation *gif, } frame_scanline = frame_data + offset_x + (decode_y * gif->width); - /* Rather than decoding pixel by pixel, we try to burst - * out streams of data to remove the need for end-of - * data checks every pixel. - */ x = width; while (x > 0) { - unsigned int burst_bytes = (stack_pos - stack_base); - if (burst_bytes > 0) { + if (written > 0) { + unsigned burst_bytes = written; if (burst_bytes > x) { burst_bytes = x; } x -= burst_bytes; + written -= burst_bytes; while (burst_bytes-- > 0) { register unsigned char colour; - colour = *--stack_pos; + colour = *stack_pos++; if (((gif->frames[frame].transparency) && (colour != gif->frames[frame].transparency_index)) || (!gif->frames[frame].transparency)) { @@ -678,7 +677,7 @@ gif__decode(gif_animation *gif, frame_scanline++; } } else { - res = lzw_decode(gif->lzw_ctx, &stack_pos); + res = lzw_decode(gif->lzw_ctx, &written); if (res != LZW_OK) { /* Unexpected end of frame, try to recover */ if (res == LZW_OK_EOD) { @@ -688,10 +687,10 @@ gif__decode(gif_animation *gif, } break; } + stack_pos = stack_base; } } } - return ret; } diff --git a/libvips/foreign/libnsgif/lzw.c b/libvips/foreign/libnsgif/lzw.c index 7032501a..518756c6 100644 --- a/libvips/foreign/libnsgif/lzw.c +++ b/libvips/foreign/libnsgif/lzw.c @@ -83,6 +83,7 @@ struct lzw_ctx { uint32_t table_size; /**< Next position in table to fill. */ /** Output value stack. */ + uint32_t written; uint8_t stack_base[LZW_TABLE_ENTRY_MAX]; /** LZW code table. Generated during decode. */ @@ -223,15 +224,12 @@ static inline lzw_result lzw__read_code( * Clear LZW code table. * * \param[in] ctx LZW reading context, updated. - * \param[out] stack_pos_out Returns current stack position. * \return LZW_OK or error code. */ static lzw_result lzw__clear_codes( - struct lzw_ctx *ctx, - const uint8_t ** const stack_pos_out) + struct lzw_ctx *ctx) { uint32_t code; - uint8_t *stack_pos; /* Reset table building context */ ctx->code_size = ctx->initial_code_size; @@ -258,10 +256,8 @@ static lzw_result lzw__clear_codes( ctx->prev_code_count = 1; /* Reset the stack, and add first non-clear code added as first item. */ - stack_pos = ctx->stack_base; - *stack_pos++ = code; + ctx->stack_base[ctx->written++] = code; - *stack_pos_out = stack_pos; return LZW_OK; } @@ -273,8 +269,7 @@ lzw_result lzw_decode_init( uint32_t compressed_data_len, uint32_t compressed_data_pos, uint8_t minimum_code_size, - const uint8_t ** const stack_base_out, - const uint8_t ** const stack_pos_out) + const uint8_t ** const stack_base_out) { struct lzw_table_entry *table = ctx->table; @@ -303,8 +298,10 @@ lzw_result lzw_decode_init( table[i].count = 1; } + ctx->written = 0; + *stack_base_out = ctx->stack_base; - return lzw__clear_codes(ctx, stack_pos_out); + return lzw__clear_codes(ctx); } /** @@ -332,32 +329,28 @@ static inline void lzw__table_add_entry( * * \param[in] ctx LZW reading context, updated. * \param[in] code LZW code to output values for. - * \param[out] stack_pos_out Returns current stack position. - * There are `stack_pos_out - ctx->stack_base` - * current stack entries. */ static inline void lzw__write_pixels(struct lzw_ctx *ctx, - uint32_t code, - const uint8_t ** const stack_pos_out) + uint32_t code) { - uint8_t *stack_pos = ctx->stack_base; - uint32_t clear_code = ctx->clear_code; + uint8_t *stack_pos = ctx->stack_base + ctx->written; struct lzw_table_entry * const table = ctx->table; + uint32_t count = table[code].count; - while (code > clear_code) { + stack_pos += count; + for (unsigned i = count; i != 0; i--) { struct lzw_table_entry *entry = table + code; - *stack_pos++ = entry->value; + *--stack_pos = entry->value; code = entry->extends; } - *stack_pos++ = table[code].value; - *stack_pos_out = stack_pos; + ctx->written += count; return; } /* Exported function, documented in lzw.h */ lzw_result lzw_decode(struct lzw_ctx *ctx, - const uint8_t ** const stack_pos_out) + uint32_t *written) { lzw_result res; uint32_t code; @@ -371,7 +364,12 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, /* Handle the new code */ if (code == ctx->clear_code) { /* Got Clear code */ - return lzw__clear_codes(ctx, stack_pos_out); + res = lzw__clear_codes(ctx); + if (res == LZW_OK) { + *written = ctx->written; + ctx->written = 0; + } + return res; } else if (code == ctx->eoi_code) { /* Got End of Information code */ @@ -400,6 +398,10 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, ctx->prev_code_count = ctx->table[code].count; ctx->prev_code = code; - lzw__write_pixels(ctx, code, stack_pos_out); + lzw__write_pixels(ctx, code); + + *written = ctx->written; + ctx->written = 0; + return LZW_OK; } diff --git a/libvips/foreign/libnsgif/lzw.h b/libvips/foreign/libnsgif/lzw.h index 888526e7..a4a58fc8 100644 --- a/libvips/foreign/libnsgif/lzw.h +++ b/libvips/foreign/libnsgif/lzw.h @@ -67,9 +67,6 @@ void lzw_context_destroy( * of a size byte at sub-block start. * \param[in] minimum_code_size The LZW Minimum Code Size. * \param[out] stack_base_out Returns base of decompressed data stack. - * \param[out] stack_pos_out Returns current stack position. - * There are `stack_pos_out - stack_base_out` - * current stack entries. * \return LZW_OK on success, or appropriate error code otherwise. */ lzw_result lzw_decode_init( @@ -78,8 +75,7 @@ lzw_result lzw_decode_init( uint32_t compressed_data_len, uint32_t compressed_data_pos, uint8_t minimum_code_size, - const uint8_t ** const stack_base_out, - const uint8_t ** const stack_pos_out); + const uint8_t ** const stack_base_out); /** * Fill the LZW stack with decompressed data @@ -87,19 +83,14 @@ lzw_result lzw_decode_init( * Ensure anything on the stack is used before calling this, as anything * on the stack before this call will be trampled. * - * Caller does not own `stack_pos_out`. - * - * \param[in] ctx LZW reading context, updated. - * \param[out] stack_pos_out Returns current stack position. - * Use with `stack_base_out` value from previous - * lzw_decode_init() call. - * There are `stack_pos_out - stack_base_out` - * current stack entries. + * \param[in] ctx LZW reading context, updated. + * \param[out] written Returns the number of values written. + * Use with `stack_base_out` value from previous + * lzw_decode_init() call. * \return LZW_OK on success, or appropriate error code otherwise. */ lzw_result lzw_decode( struct lzw_ctx *ctx, - const uint8_t ** const stack_pos_out); - + uint32_t *written); #endif From 64ddf20599de4930df7b164ee994be06149f5f88 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Sat, 3 Apr 2021 22:11:58 +0100 Subject: [PATCH 28/41] lzw: Adapt main code handling to handle clear codes too. --- libvips/foreign/libnsgif/lzw.c | 78 +++++++++++----------------------- 1 file changed, 25 insertions(+), 53 deletions(-) diff --git a/libvips/foreign/libnsgif/lzw.c b/libvips/foreign/libnsgif/lzw.c index 518756c6..ac710a78 100644 --- a/libvips/foreign/libnsgif/lzw.c +++ b/libvips/foreign/libnsgif/lzw.c @@ -223,42 +223,16 @@ static inline lzw_result lzw__read_code( /** * Clear LZW code table. * - * \param[in] ctx LZW reading context, updated. + * \param[in] ctx LZW reading context, updated. * \return LZW_OK or error code. */ -static lzw_result lzw__clear_codes( +static inline void lzw__clear_table( struct lzw_ctx *ctx) { - uint32_t code; - /* Reset table building context */ ctx->code_size = ctx->initial_code_size; ctx->code_max = (1 << ctx->initial_code_size) - 1; ctx->table_size = ctx->eoi_code + 1; - - /* There might be a sequence of clear codes, so process them all */ - do { - lzw_result res = lzw__read_code(&ctx->input, - ctx->code_size, &code); - if (res != LZW_OK) { - return res; - } - } while (code == ctx->clear_code); - - /* The initial code must be from the initial table. */ - if (code > ctx->clear_code) { - return LZW_BAD_ICODE; - } - - /* Record this initial code as "previous" code, needed during decode. */ - ctx->prev_code = code; - ctx->prev_code_first = code; - ctx->prev_code_count = 1; - - /* Reset the stack, and add first non-clear code added as first item. */ - ctx->stack_base[ctx->written++] = code; - - return LZW_OK; } @@ -298,10 +272,11 @@ lzw_result lzw_decode_init( table[i].count = 1; } - ctx->written = 0; + lzw__clear_table(ctx); + ctx->prev_code = ctx->clear_code; *stack_base_out = ctx->stack_base; - return lzw__clear_codes(ctx); + return LZW_OK; } /** @@ -355,6 +330,8 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, lzw_result res; uint32_t code; + ctx->written = 0; + /* Get a new code from the input */ res = lzw__read_code(&ctx->input, ctx->code_size, &code); if (res != LZW_OK) { @@ -362,35 +339,33 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, } /* Handle the new code */ - if (code == ctx->clear_code) { - /* Got Clear code */ - res = lzw__clear_codes(ctx); - if (res == LZW_OK) { - *written = ctx->written; - ctx->written = 0; - } - return res; - - } else if (code == ctx->eoi_code) { + if (code == ctx->eoi_code) { /* Got End of Information code */ return LZW_EOI_CODE; } else if (code > ctx->table_size) { /* Code is invalid */ return LZW_BAD_CODE; - } - if (ctx->table_size < LZW_TABLE_ENTRY_MAX) { - uint32_t size = ctx->table_size; - lzw__table_add_entry(ctx, (code < size) ? - ctx->table[code].first : - ctx->prev_code_first); + } else if (code == ctx->clear_code) { + lzw__clear_table(ctx); + } else { + if (ctx->prev_code != ctx->clear_code && + ctx->table_size < LZW_TABLE_ENTRY_MAX) { + uint32_t size = ctx->table_size; + lzw__table_add_entry(ctx, (code < size) ? + ctx->table[code].first : + ctx->prev_code_first); - /* Ensure code size is increased, if needed. */ - if (size == ctx->code_max && ctx->code_size < LZW_CODE_MAX) { - ctx->code_size++; - ctx->code_max = (1 << ctx->code_size) - 1; + /* Ensure code size is increased, if needed. */ + if (size == ctx->code_max && + ctx->code_size < LZW_CODE_MAX) { + ctx->code_size++; + ctx->code_max = (1 << ctx->code_size) - 1; + } } + + lzw__write_pixels(ctx, code); } /* Store details of this code as "previous code" to the context. */ @@ -398,10 +373,7 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, ctx->prev_code_count = ctx->table[code].count; ctx->prev_code = code; - lzw__write_pixels(ctx, code); - *written = ctx->written; - ctx->written = 0; return LZW_OK; } From 663bf07aac25a38377e1c14c5a390be6b4df89f0 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Sun, 4 Apr 2021 10:37:13 +0100 Subject: [PATCH 29/41] lzw: Remove written member from context. Not needed now that clear codes are handled normally. --- libvips/foreign/libnsgif/lzw.c | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/libvips/foreign/libnsgif/lzw.c b/libvips/foreign/libnsgif/lzw.c index ac710a78..a5892218 100644 --- a/libvips/foreign/libnsgif/lzw.c +++ b/libvips/foreign/libnsgif/lzw.c @@ -83,7 +83,6 @@ struct lzw_ctx { uint32_t table_size; /**< Next position in table to fill. */ /** Output value stack. */ - uint32_t written; uint8_t stack_base[LZW_TABLE_ENTRY_MAX]; /** LZW code table. Generated during decode. */ @@ -302,13 +301,14 @@ static inline void lzw__table_add_entry( /** * Write values for this code to the output stack. * - * \param[in] ctx LZW reading context, updated. - * \param[in] code LZW code to output values for. + * \param[in] ctx LZW reading context, updated. + * \param[in] code LZW code to output values for. + * \return Number of pixel values written. */ -static inline void lzw__write_pixels(struct lzw_ctx *ctx, +static inline uint32_t lzw__write_pixels(struct lzw_ctx *ctx, uint32_t code) { - uint8_t *stack_pos = ctx->stack_base + ctx->written; + uint8_t *stack_pos = ctx->stack_base; struct lzw_table_entry * const table = ctx->table; uint32_t count = table[code].count; @@ -319,8 +319,7 @@ static inline void lzw__write_pixels(struct lzw_ctx *ctx, code = entry->extends; } - ctx->written += count; - return; + return count; } /* Exported function, documented in lzw.h */ @@ -330,8 +329,6 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, lzw_result res; uint32_t code; - ctx->written = 0; - /* Get a new code from the input */ res = lzw__read_code(&ctx->input, ctx->code_size, &code); if (res != LZW_OK) { @@ -365,7 +362,7 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, } } - lzw__write_pixels(ctx, code); + *written += lzw__write_pixels(ctx, code); } /* Store details of this code as "previous code" to the context. */ @@ -373,7 +370,5 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, ctx->prev_code_count = ctx->table[code].count; ctx->prev_code = code; - *written = ctx->written; - return LZW_OK; } From 7426c3d5004f419f4ac4d872e9f5699f4016f92b Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Sun, 4 Apr 2021 11:54:22 +0100 Subject: [PATCH 30/41] gif: Frame decoding: Simplify transparency check. --- libvips/foreign/libnsgif/libnsgif.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/libvips/foreign/libnsgif/libnsgif.c b/libvips/foreign/libnsgif/libnsgif.c index 52a83e2f..fd064471 100644 --- a/libvips/foreign/libnsgif/libnsgif.c +++ b/libvips/foreign/libnsgif/libnsgif.c @@ -42,6 +42,9 @@ /** Transparent colour */ #define GIF_TRANSPARENT_COLOUR 0x00 +/** No transparency */ +#define GIF_NO_TRANSPARENCY (0xFFFFFFFFu) + /* GIF Flags */ #define GIF_FRAME_COMBINE 1 #define GIF_FRAME_CLEAR 2 @@ -630,6 +633,7 @@ gif__decode(gif_animation *gif, unsigned int *restrict frame_data, unsigned int *restrict colour_table) { + unsigned int transparency_index; const uint8_t *stack_base; const uint8_t *stack_pos; uint32_t written = 0; @@ -644,6 +648,10 @@ gif__decode(gif_animation *gif, return gif_error_from_lzw(res); } + transparency_index = gif->frames[frame].transparency ? + gif->frames[frame].transparency_index : + GIF_NO_TRANSPARENCY; + stack_pos = stack_base; for (unsigned int y = 0; y < height; y++) { unsigned int x; @@ -667,11 +675,9 @@ gif__decode(gif_animation *gif, x -= burst_bytes; written -= burst_bytes; while (burst_bytes-- > 0) { - register unsigned char colour; + register unsigned int colour; colour = *stack_pos++; - if (((gif->frames[frame].transparency) && - (colour != gif->frames[frame].transparency_index)) || - (!gif->frames[frame].transparency)) { + if (colour != transparency_index) { *frame_scanline = colour_table[colour]; } frame_scanline++; From 01fde32f99b84a3cbdf4529c8f037cd20094d173 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Sun, 4 Apr 2021 12:11:23 +0100 Subject: [PATCH 31/41] gif: Frame decoding: Rearrange for readability. --- libvips/foreign/libnsgif/libnsgif.c | 34 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/libvips/foreign/libnsgif/libnsgif.c b/libvips/foreign/libnsgif/libnsgif.c index fd064471..bd751fd8 100644 --- a/libvips/foreign/libnsgif/libnsgif.c +++ b/libvips/foreign/libnsgif/libnsgif.c @@ -636,7 +636,7 @@ gif__decode(gif_animation *gif, unsigned int transparency_index; const uint8_t *stack_base; const uint8_t *stack_pos; - uint32_t written = 0; + uint32_t available = 0; gif_result ret = GIF_OK; lzw_result res; @@ -667,23 +667,9 @@ gif__decode(gif_animation *gif, x = width; while (x > 0) { - if (written > 0) { - unsigned burst_bytes = written; - if (burst_bytes > x) { - burst_bytes = x; - } - x -= burst_bytes; - written -= burst_bytes; - while (burst_bytes-- > 0) { - register unsigned int colour; - colour = *stack_pos++; - if (colour != transparency_index) { - *frame_scanline = colour_table[colour]; - } - frame_scanline++; - } - } else { - res = lzw_decode(gif->lzw_ctx, &written); + unsigned row_available; + if (available == 0) { + res = lzw_decode(gif->lzw_ctx, &available); if (res != LZW_OK) { /* Unexpected end of frame, try to recover */ if (res == LZW_OK_EOD) { @@ -695,6 +681,18 @@ gif__decode(gif_animation *gif, } stack_pos = stack_base; } + + row_available = x < available ? x : available; + x -= row_available; + available -= row_available; + while (row_available-- > 0) { + register unsigned int colour; + colour = *stack_pos++; + if (colour != transparency_index) { + *frame_scanline = colour_table[colour]; + } + frame_scanline++; + } } } return ret; From abd8619bef4ebe6442e90ac8d0f6a4736601ebd4 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Mon, 5 Apr 2021 10:11:07 +0100 Subject: [PATCH 32/41] lzw: Return output array from decode function instead of init. --- libvips/foreign/libnsgif/libnsgif.c | 12 +++----- libvips/foreign/libnsgif/lzw.c | 47 +++++++++++++++++++++-------- libvips/foreign/libnsgif/lzw.h | 22 ++++++-------- 3 files changed, 50 insertions(+), 31 deletions(-) diff --git a/libvips/foreign/libnsgif/libnsgif.c b/libvips/foreign/libnsgif/libnsgif.c index bd751fd8..89e30d4c 100644 --- a/libvips/foreign/libnsgif/libnsgif.c +++ b/libvips/foreign/libnsgif/libnsgif.c @@ -634,8 +634,6 @@ gif__decode(gif_animation *gif, unsigned int *restrict colour_table) { unsigned int transparency_index; - const uint8_t *stack_base; - const uint8_t *stack_pos; uint32_t available = 0; gif_result ret = GIF_OK; lzw_result res; @@ -643,7 +641,7 @@ gif__decode(gif_animation *gif, /* Initialise the LZW decoding */ res = lzw_decode_init(gif->lzw_ctx, gif->gif_data, gif->buffer_size, gif->buffer_position, - minimum_code_size, &stack_base); + minimum_code_size); if (res != LZW_OK) { return gif_error_from_lzw(res); } @@ -652,7 +650,6 @@ gif__decode(gif_animation *gif, gif->frames[frame].transparency_index : GIF_NO_TRANSPARENCY; - stack_pos = stack_base; for (unsigned int y = 0; y < height; y++) { unsigned int x; unsigned int decode_y; @@ -667,9 +664,11 @@ gif__decode(gif_animation *gif, x = width; while (x > 0) { + const uint8_t *uncompressed; unsigned row_available; if (available == 0) { - res = lzw_decode(gif->lzw_ctx, &available); + res = lzw_decode(gif->lzw_ctx, + &uncompressed, &available); if (res != LZW_OK) { /* Unexpected end of frame, try to recover */ if (res == LZW_OK_EOD) { @@ -679,7 +678,6 @@ gif__decode(gif_animation *gif, } break; } - stack_pos = stack_base; } row_available = x < available ? x : available; @@ -687,7 +685,7 @@ gif__decode(gif_animation *gif, available -= row_available; while (row_available-- > 0) { register unsigned int colour; - colour = *stack_pos++; + colour = *uncompressed++; if (colour != transparency_index) { *frame_scanline = colour_table[colour]; } diff --git a/libvips/foreign/libnsgif/lzw.c b/libvips/foreign/libnsgif/lzw.c index a5892218..035882d3 100644 --- a/libvips/foreign/libnsgif/lzw.c +++ b/libvips/foreign/libnsgif/lzw.c @@ -241,8 +241,7 @@ lzw_result lzw_decode_init( const uint8_t *compressed_data, uint32_t compressed_data_len, uint32_t compressed_data_pos, - uint8_t minimum_code_size, - const uint8_t ** const stack_base_out) + uint8_t minimum_code_size) { struct lzw_table_entry *table = ctx->table; @@ -274,7 +273,6 @@ lzw_result lzw_decode_init( lzw__clear_table(ctx); ctx->prev_code = ctx->clear_code; - *stack_base_out = ctx->stack_base; return LZW_OK; } @@ -301,30 +299,45 @@ static inline void lzw__table_add_entry( /** * Write values for this code to the output stack. * - * \param[in] ctx LZW reading context, updated. - * \param[in] code LZW code to output values for. + * \param[in] ctx LZW reading context, updated. + * \param[in] output Array to write output values into. + * \param[in] code LZW code to output values for. * \return Number of pixel values written. */ static inline uint32_t lzw__write_pixels(struct lzw_ctx *ctx, + void *restrict output, uint32_t code) { - uint8_t *stack_pos = ctx->stack_base; + uint8_t *restrict output_pos = (uint8_t *)output; struct lzw_table_entry * const table = ctx->table; uint32_t count = table[code].count; - stack_pos += count; + output_pos += count; for (unsigned i = count; i != 0; i--) { struct lzw_table_entry *entry = table + code; - *--stack_pos = entry->value; + *--output_pos = entry->value; code = entry->extends; } return count; } -/* Exported function, documented in lzw.h */ -lzw_result lzw_decode(struct lzw_ctx *ctx, - uint32_t *written) +/** + * Fill the LZW stack with decompressed data + * + * Ensure anything in output is used before calling this, as anything + * on the there before this call will be trampled. + * + * \param[in] ctx LZW reading context, updated. + * \param[in] output Array to write output values into. + * \param[out] used Returns the number of values written. + * Use with `stack_base_out` value from previous + * lzw_decode_init() call. + * \return LZW_OK on success, or appropriate error code otherwise. + */ +static inline lzw_result lzw__decode(struct lzw_ctx *ctx, + uint8_t *restrict output, + uint32_t *restrict used) { lzw_result res; uint32_t code; @@ -362,7 +375,7 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, } } - *written += lzw__write_pixels(ctx, code); + *used += lzw__write_pixels(ctx, output, code); } /* Store details of this code as "previous code" to the context. */ @@ -372,3 +385,13 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, return LZW_OK; } + +/* Exported function, documented in lzw.h */ +lzw_result lzw_decode(struct lzw_ctx *ctx, + const uint8_t *restrict* const restrict data, + uint32_t *restrict used) +{ + *used = 0; + *data = ctx->stack_base; + return lzw__decode(ctx, ctx->stack_base, used); +} diff --git a/libvips/foreign/libnsgif/lzw.h b/libvips/foreign/libnsgif/lzw.h index a4a58fc8..dd0191ef 100644 --- a/libvips/foreign/libnsgif/lzw.h +++ b/libvips/foreign/libnsgif/lzw.h @@ -74,23 +74,21 @@ lzw_result lzw_decode_init( const uint8_t *compressed_data, uint32_t compressed_data_len, uint32_t compressed_data_pos, - uint8_t minimum_code_size, - const uint8_t ** const stack_base_out); + uint8_t minimum_code_size); /** - * Fill the LZW stack with decompressed data + * Read a single LZW code and write into lzw context owned output buffer. * - * Ensure anything on the stack is used before calling this, as anything - * on the stack before this call will be trampled. + * Ensure anything in output is used before calling this, as anything + * on the there before this call will be trampled. * - * \param[in] ctx LZW reading context, updated. - * \param[out] written Returns the number of values written. - * Use with `stack_base_out` value from previous - * lzw_decode_init() call. + * \param[in] ctx LZW reading context, updated. + * \param[out] data Returns pointer to array of output values. + * \param[out] used Returns the number of values written to data. * \return LZW_OK on success, or appropriate error code otherwise. */ -lzw_result lzw_decode( - struct lzw_ctx *ctx, - uint32_t *written); +lzw_result lzw_decode(struct lzw_ctx *ctx, + const uint8_t *restrict *const restrict data, + uint32_t *restrict used); #endif From ed3363b89952fe99648c99b16d797c54e807d394 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Mon, 5 Apr 2021 10:31:46 +0100 Subject: [PATCH 33/41] lzw: Add support for resumable output of a single code. This allows handling of insufficient output buffer space. --- libvips/foreign/libnsgif/lzw.c | 112 ++++++++++++++++++++++----------- 1 file changed, 77 insertions(+), 35 deletions(-) diff --git a/libvips/foreign/libnsgif/lzw.c b/libvips/foreign/libnsgif/lzw.c index 035882d3..5308ff45 100644 --- a/libvips/foreign/libnsgif/lzw.c +++ b/libvips/foreign/libnsgif/lzw.c @@ -82,6 +82,9 @@ struct lzw_ctx { uint32_t table_size; /**< Next position in table to fill. */ + uint32_t output_code; /**< Code that has been partially output. */ + uint32_t output_left; /**< Number of values left for output_code. */ + /** Output value stack. */ uint8_t stack_base[LZW_TABLE_ENTRY_MAX]; @@ -263,6 +266,8 @@ lzw_result lzw_decode_init( ctx->clear_code = (1 << minimum_code_size) + 0; ctx->eoi_code = (1 << minimum_code_size) + 1; + ctx->output_left = 0; + /* Initialise the standard table entries */ for (uint32_t i = 0; i < ctx->clear_code; ++i) { table[i].first = i; @@ -296,47 +301,28 @@ static inline void lzw__table_add_entry( ctx->table_size++; } -/** - * Write values for this code to the output stack. - * - * \param[in] ctx LZW reading context, updated. - * \param[in] output Array to write output values into. - * \param[in] code LZW code to output values for. - * \return Number of pixel values written. - */ -static inline uint32_t lzw__write_pixels(struct lzw_ctx *ctx, +typedef uint32_t (*lzw_writer_fn)( + struct lzw_ctx *ctx, void *restrict output, - uint32_t code) -{ - uint8_t *restrict output_pos = (uint8_t *)output; - struct lzw_table_entry * const table = ctx->table; - uint32_t count = table[code].count; - - output_pos += count; - for (unsigned i = count; i != 0; i--) { - struct lzw_table_entry *entry = table + code; - *--output_pos = entry->value; - code = entry->extends; - } - - return count; -} + uint32_t length, + uint32_t used, + uint32_t code, + uint32_t left); /** - * Fill the LZW stack with decompressed data + * Get the next LZW code and write its value(s) to output buffer. * - * Ensure anything in output is used before calling this, as anything - * on the there before this call will be trampled. - * - * \param[in] ctx LZW reading context, updated. - * \param[in] output Array to write output values into. - * \param[out] used Returns the number of values written. - * Use with `stack_base_out` value from previous - * lzw_decode_init() call. + * \param[in] ctx LZW reading context, updated. + * \param[in] output Array to write output values into. + * \param[in] length Size of output array. + * \param[in] write_pixels Function for writing pixels to output. + * \param[in,out] used Number of values written. Updated on exit. * \return LZW_OK on success, or appropriate error code otherwise. */ static inline lzw_result lzw__decode(struct lzw_ctx *ctx, uint8_t *restrict output, + uint32_t length, + lzw_writer_fn write_pixels, uint32_t *restrict used) { lzw_result res; @@ -375,7 +361,8 @@ static inline lzw_result lzw__decode(struct lzw_ctx *ctx, } } - *used += lzw__write_pixels(ctx, output, code); + *used += write_pixels(ctx, output, length, *used, code, + ctx->table[code].count); } /* Store details of this code as "previous code" to the context. */ @@ -386,6 +373,60 @@ static inline lzw_result lzw__decode(struct lzw_ctx *ctx, return LZW_OK; } +/** + * Write values for this code to the output stack. + * + * If there isn't enough space in the output stack, this function will write + * the as many as it can into the output. If `ctx->output_left > 0` after + * this call, then there is more data for this code left to output. The code + * is stored to the context as `ctx->output_code`. + * + * \param[in] ctx LZW reading context, updated. + * \param[in] output Array to write output values into. + * \param[in] length Size of output array. + * \param[in] used Current position in output array. + * \param[in] code LZW code to output values for. + * \param[in] left Number of values remaining to output for this value. + * \return Number of pixel values written. + */ +static inline uint32_t lzw__write_pixels(struct lzw_ctx *ctx, + void *restrict output, + uint32_t length, + uint32_t used, + uint32_t code, + uint32_t left) +{ + uint8_t *restrict output_pos = (uint8_t *)output + used; + struct lzw_table_entry * const table = ctx->table; + uint32_t space = length - used; + uint32_t count = left; + + if (count > space) { + left = count - space; + count = space; + } else { + left = 0; + } + + ctx->output_code = code; + ctx->output_left = left; + + /* Skip over any values we don't have space for. */ + for (unsigned i = left; i != 0; i--) { + struct lzw_table_entry *entry = table + code; + code = entry->extends; + } + + output_pos += count; + for (unsigned i = count; i != 0; i--) { + struct lzw_table_entry *entry = table + code; + *--output_pos = entry->value; + code = entry->extends; + } + + return count; +} + /* Exported function, documented in lzw.h */ lzw_result lzw_decode(struct lzw_ctx *ctx, const uint8_t *restrict* const restrict data, @@ -393,5 +434,6 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, { *used = 0; *data = ctx->stack_base; - return lzw__decode(ctx, ctx->stack_base, used); + return lzw__decode(ctx, ctx->stack_base, sizeof(ctx->stack_base), + lzw__write_pixels, used); } From 5426e9b81a9847f281ec5a4c74d0b3ed96f84b77 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Sun, 4 Apr 2021 22:27:52 +0100 Subject: [PATCH 34/41] lzw: Add function for decoding multiple LZW codes at a time. --- libvips/foreign/libnsgif/lzw.c | 26 ++++++++++++++++++++++++++ libvips/foreign/libnsgif/lzw.h | 15 +++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/libvips/foreign/libnsgif/lzw.c b/libvips/foreign/libnsgif/lzw.c index 5308ff45..88ee0607 100644 --- a/libvips/foreign/libnsgif/lzw.c +++ b/libvips/foreign/libnsgif/lzw.c @@ -437,3 +437,29 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, return lzw__decode(ctx, ctx->stack_base, sizeof(ctx->stack_base), lzw__write_pixels, used); } + +/* Exported function, documented in lzw.h */ +lzw_result lzw_decode_continuous(struct lzw_ctx *ctx, + const uint8_t ** const data, + uint32_t *restrict used) +{ + *used = 0; + *data = ctx->stack_base; + + if (ctx->output_left != 0) { + *used += lzw__write_pixels(ctx, + ctx->stack_base, sizeof(ctx->stack_base), *used, + ctx->output_code, ctx->output_left); + } + + while (*used != sizeof(ctx->stack_base)) { + lzw_result res = lzw__decode(ctx, + ctx->stack_base, sizeof(ctx->stack_base), + lzw__write_pixels, used); + if (res != LZW_OK) { + return res; + } + } + + return LZW_OK; +} diff --git a/libvips/foreign/libnsgif/lzw.h b/libvips/foreign/libnsgif/lzw.h index dd0191ef..9f8f979a 100644 --- a/libvips/foreign/libnsgif/lzw.h +++ b/libvips/foreign/libnsgif/lzw.h @@ -91,4 +91,19 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, const uint8_t *restrict *const restrict data, uint32_t *restrict used); +/** + * Read input codes until end of lzw context owned output buffer. + * + * Ensure anything in output is used before calling this, as anything + * on the there before this call will be trampled. + * + * \param[in] ctx LZW reading context, updated. + * \param[out] data Returns pointer to array of output values. + * \param[out] used Returns the number of values written to data. + * \return LZW_OK on success, or appropriate error code otherwise. + */ +lzw_result lzw_decode_continuous(struct lzw_ctx *ctx, + const uint8_t ** const data, + uint32_t *restrict used); + #endif From 0d394487c1a218315be495efa424bb17eeb25d40 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Sun, 4 Apr 2021 22:29:34 +0100 Subject: [PATCH 35/41] gif: Handle any uncompressed output before exiting due to error. --- libvips/foreign/libnsgif/libnsgif.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libvips/foreign/libnsgif/libnsgif.c b/libvips/foreign/libnsgif/libnsgif.c index 89e30d4c..aaf98264 100644 --- a/libvips/foreign/libnsgif/libnsgif.c +++ b/libvips/foreign/libnsgif/libnsgif.c @@ -667,8 +667,6 @@ gif__decode(gif_animation *gif, const uint8_t *uncompressed; unsigned row_available; if (available == 0) { - res = lzw_decode(gif->lzw_ctx, - &uncompressed, &available); if (res != LZW_OK) { /* Unexpected end of frame, try to recover */ if (res == LZW_OK_EOD) { @@ -678,6 +676,8 @@ gif__decode(gif_animation *gif, } break; } + res = lzw_decode(gif->lzw_ctx, + &uncompressed, &available); } row_available = x < available ? x : available; From 05fddf49e3fa586e6c8a09afcfcc50a0de00abc9 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Sun, 4 Apr 2021 23:40:01 +0100 Subject: [PATCH 36/41] lzw: Direct output into frame data, avoiding stack. If the frame is non-interlaced, and has the same rowstride as the full image, then we can decode lzw directly into the output image. --- libvips/foreign/libnsgif/libnsgif.c | 83 +++++++++++++++++++++++++- libvips/foreign/libnsgif/lzw.c | 90 ++++++++++++++++++++++++++++- libvips/foreign/libnsgif/lzw.h | 25 ++++++++ 3 files changed, 195 insertions(+), 3 deletions(-) diff --git a/libvips/foreign/libnsgif/libnsgif.c b/libvips/foreign/libnsgif/libnsgif.c index aaf98264..95f3afb2 100644 --- a/libvips/foreign/libnsgif/libnsgif.c +++ b/libvips/foreign/libnsgif/libnsgif.c @@ -621,8 +621,8 @@ static gif_result gif__recover_previous_frame(const gif_animation *gif) return GIF_OK; } -static inline gif_result -gif__decode(gif_animation *gif, +static gif_result +gif__decode_complex(gif_animation *gif, unsigned int frame, unsigned int width, unsigned int height, @@ -696,6 +696,85 @@ gif__decode(gif_animation *gif, return ret; } +static gif_result +gif__decode_simple(gif_animation *gif, + unsigned int frame, + unsigned int height, + unsigned int offset_y, + uint8_t minimum_code_size, + unsigned int *restrict frame_data, + unsigned int *restrict colour_table) +{ + unsigned int transparency_index; + uint32_t pixels = gif->width * height; + uint32_t written = 0; + gif_result ret = GIF_OK; + lzw_result res; + + /* Initialise the LZW decoding */ + res = lzw_decode_init(gif->lzw_ctx, gif->gif_data, + gif->buffer_size, gif->buffer_position, + minimum_code_size); + if (res != LZW_OK) { + return gif_error_from_lzw(res); + } + + transparency_index = gif->frames[frame].transparency ? + gif->frames[frame].transparency_index : + GIF_NO_TRANSPARENCY; + + frame_data += (offset_y * gif->width); + + while (pixels > 0) { + res = lzw_decode_map_continuous(gif->lzw_ctx, + transparency_index, colour_table, + frame_data, pixels, &written); + pixels -= written; + frame_data += written; + if (res != LZW_OK) { + /* Unexpected end of frame, try to recover */ + if (res == LZW_OK_EOD) { + ret = GIF_OK; + } else { + ret = gif_error_from_lzw(res); + } + break; + } + } + + if (pixels == 0) { + ret = GIF_OK; + } + + return ret; +} + +static inline gif_result +gif__decode(gif_animation *gif, + unsigned int frame, + unsigned int width, + unsigned int height, + unsigned int offset_x, + unsigned int offset_y, + unsigned int interlace, + uint8_t minimum_code_size, + unsigned int *restrict frame_data, + unsigned int *restrict colour_table) +{ + gif_result ret; + + if (interlace == false && width == gif->width && offset_x == 0) { + ret = gif__decode_simple(gif, frame, height, offset_y, + minimum_code_size, frame_data, colour_table); + } else { + ret = gif__decode_complex(gif, frame, width, height, + offset_x, offset_y, interlace, + minimum_code_size, frame_data, colour_table); + } + + return ret; +} + /** * decode a gif frame * diff --git a/libvips/foreign/libnsgif/lzw.c b/libvips/foreign/libnsgif/lzw.c index 88ee0607..ece06e7c 100644 --- a/libvips/foreign/libnsgif/lzw.c +++ b/libvips/foreign/libnsgif/lzw.c @@ -85,6 +85,9 @@ struct lzw_ctx { uint32_t output_code; /**< Code that has been partially output. */ uint32_t output_left; /**< Number of values left for output_code. */ + uint32_t transparency_idx; /**< Index representing transparency. */ + uint32_t *restrict colour_map; /**< Index to pixel colour mapping */ + /** Output value stack. */ uint8_t stack_base[LZW_TABLE_ENTRY_MAX]; @@ -320,7 +323,7 @@ typedef uint32_t (*lzw_writer_fn)( * \return LZW_OK on success, or appropriate error code otherwise. */ static inline lzw_result lzw__decode(struct lzw_ctx *ctx, - uint8_t *restrict output, + void *restrict output, uint32_t length, lzw_writer_fn write_pixels, uint32_t *restrict used) @@ -463,3 +466,88 @@ lzw_result lzw_decode_continuous(struct lzw_ctx *ctx, return LZW_OK; } + +/** + * Write colour mapped values for this code to the output stack. + * + * If there isn't enough space in the output stack, this function will write + * the as many as it can into the output. If `ctx->output_left > 0` after + * this call, then there is more data for this code left to output. The code + * is stored to the context as `ctx->output_code`. + * + * \param[in] ctx LZW reading context, updated. + * \param[in] output Array to write output values into. + * \param[in] length Size of output array. + * \param[in] used Current position in output array. + * \param[in] code LZW code to output values for. + * \param[in] left Number of values remaining to output for this value. + * \return Number of pixel values written. + */ +static inline uint32_t lzw__write_pixels_map(struct lzw_ctx *ctx, + void *restrict buffer, + uint32_t length, + uint32_t used, + uint32_t code, + uint32_t left) +{ + uint32_t *restrict stack_pos = (uint32_t *)buffer + used; + struct lzw_table_entry * const table = ctx->table; + uint32_t space = length - used; + uint32_t count = left; + + if (count > space) { + left = count - space; + count = space; + } else { + left = 0; + } + + ctx->output_code = code; + ctx->output_left = left; + + for (unsigned i = left; i != 0; i--) { + struct lzw_table_entry *entry = table + code; + code = entry->extends; + } + + stack_pos += count; + for (unsigned i = count; i != 0; i--) { + struct lzw_table_entry *entry = table + code; + --stack_pos; + if (entry->value != ctx->transparency_idx) { + *stack_pos = ctx->colour_map[entry->value]; + } + code = entry->extends; + } + + return count; +} + +/* Exported function, documented in lzw.h */ +lzw_result lzw_decode_map_continuous(struct lzw_ctx *ctx, + uint32_t transparency_idx, + uint32_t *restrict colour_map, + uint32_t *restrict data, + uint32_t length, + uint32_t *restrict used) +{ + *used = 0; + + ctx->transparency_idx = transparency_idx; + ctx->colour_map = colour_map; + + if (ctx->output_left != 0) { + *used += lzw__write_pixels_map(ctx, data, length, *used, + ctx->output_code, ctx->output_left); + } + + while (*used != sizeof(ctx->stack_base)) { + lzw_result res = lzw__decode(ctx, data, length, + lzw__write_pixels_map, used); + if (res != LZW_OK) { + return res; + } + } + + return LZW_OK; +} diff --git a/libvips/foreign/libnsgif/lzw.h b/libvips/foreign/libnsgif/lzw.h index 9f8f979a..c442cff6 100644 --- a/libvips/foreign/libnsgif/lzw.h +++ b/libvips/foreign/libnsgif/lzw.h @@ -106,4 +106,29 @@ lzw_result lzw_decode_continuous(struct lzw_ctx *ctx, const uint8_t ** const data, uint32_t *restrict used); +/** + * Read LZW codes into client buffer, mapping output to colours. + * + * Ensure anything in output is used before calling this, as anything + * on the there before this call will be trampled. + * + * For transparency to work correctly, the given client buffer must have + * the values from the previous frame. The transparency_idx should be a value + * of 256 or above, if the frame does not have transparency. + * + * \param[in] ctx LZW reading context, updated. + * \param[in] transparency_idx Index representing transparency. + * \param[in] colour_map Index to pixel colour mapping + * \param[in] data Client buffer to fill with colour mapped values. + * \param[in] length Size of output array. + * \param[out] used Returns the number of values written to data. + * \return LZW_OK on success, or appropriate error code otherwise. + */ +lzw_result lzw_decode_map_continuous(struct lzw_ctx *ctx, + uint32_t transparency_idx, + uint32_t *restrict colour_table, + uint32_t *restrict data, + uint32_t length, + uint32_t *restrict used); + #endif From 020fc32108cb787d601a6d0492b53dbed2499b11 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Mon, 5 Apr 2021 16:06:37 +0100 Subject: [PATCH 37/41] gif: Switch complex decoder over to continuous lzw API. --- libvips/foreign/libnsgif/libnsgif.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libvips/foreign/libnsgif/libnsgif.c b/libvips/foreign/libnsgif/libnsgif.c index 95f3afb2..382ae33f 100644 --- a/libvips/foreign/libnsgif/libnsgif.c +++ b/libvips/foreign/libnsgif/libnsgif.c @@ -676,7 +676,7 @@ gif__decode_complex(gif_animation *gif, } break; } - res = lzw_decode(gif->lzw_ctx, + res = lzw_decode_continuous(gif->lzw_ctx, &uncompressed, &available); } From 71eb67ba7c7408097d37a9b312885aa0d80438de Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Sat, 17 Apr 2021 15:00:47 +0100 Subject: [PATCH 38/41] lzw: Constify table pointers in writer functions. --- libvips/foreign/libnsgif/lzw.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libvips/foreign/libnsgif/lzw.c b/libvips/foreign/libnsgif/lzw.c index ece06e7c..4c4e09c0 100644 --- a/libvips/foreign/libnsgif/lzw.c +++ b/libvips/foreign/libnsgif/lzw.c @@ -400,7 +400,7 @@ static inline uint32_t lzw__write_pixels(struct lzw_ctx *ctx, uint32_t left) { uint8_t *restrict output_pos = (uint8_t *)output + used; - struct lzw_table_entry * const table = ctx->table; + const struct lzw_table_entry * const table = ctx->table; uint32_t space = length - used; uint32_t count = left; @@ -416,13 +416,13 @@ static inline uint32_t lzw__write_pixels(struct lzw_ctx *ctx, /* Skip over any values we don't have space for. */ for (unsigned i = left; i != 0; i--) { - struct lzw_table_entry *entry = table + code; + const struct lzw_table_entry *entry = table + code; code = entry->extends; } output_pos += count; for (unsigned i = count; i != 0; i--) { - struct lzw_table_entry *entry = table + code; + const struct lzw_table_entry *entry = table + code; *--output_pos = entry->value; code = entry->extends; } @@ -491,7 +491,7 @@ static inline uint32_t lzw__write_pixels_map(struct lzw_ctx *ctx, uint32_t left) { uint32_t *restrict stack_pos = (uint32_t *)buffer + used; - struct lzw_table_entry * const table = ctx->table; + const struct lzw_table_entry * const table = ctx->table; uint32_t space = length - used; uint32_t count = left; @@ -506,13 +506,13 @@ static inline uint32_t lzw__write_pixels_map(struct lzw_ctx *ctx, ctx->output_left = left; for (unsigned i = left; i != 0; i--) { - struct lzw_table_entry *entry = table + code; + const struct lzw_table_entry *entry = table + code; code = entry->extends; } stack_pos += count; for (unsigned i = count; i != 0; i--) { - struct lzw_table_entry *entry = table + code; + const struct lzw_table_entry *entry = table + code; --stack_pos; if (entry->value != ctx->transparency_idx) { *stack_pos = ctx->colour_map[entry->value]; From 98e8673dbc1029d9f817d1c9006913482e79a055 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Sat, 17 Apr 2021 17:28:32 +0100 Subject: [PATCH 39/41] lzw: Restrict pointers through code reader. --- libvips/foreign/libnsgif/lzw.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libvips/foreign/libnsgif/lzw.c b/libvips/foreign/libnsgif/lzw.c index 4c4e09c0..a3034f8a 100644 --- a/libvips/foreign/libnsgif/lzw.c +++ b/libvips/foreign/libnsgif/lzw.c @@ -36,7 +36,7 @@ * Note that an individual LZW code can be split over up to three sub-blocks. */ struct lzw_read_ctx { - const uint8_t *data; /**< Pointer to start of input data */ + const uint8_t *restrict data; /**< Pointer to start of input data */ uint32_t data_len; /**< Input data length */ uint32_t data_sb_next; /**< Offset to sub-block size */ @@ -122,7 +122,7 @@ void lzw_context_destroy(struct lzw_ctx *ctx) * \param[in] ctx LZW reading context, updated on success. * \return LZW_OK or LZW_OK_EOD on success, appropriate error otherwise. */ -static lzw_result lzw__block_advance(struct lzw_read_ctx *ctx) +static lzw_result lzw__block_advance(struct lzw_read_ctx *restrict ctx) { uint32_t block_size; uint32_t next_block_pos = ctx->data_sb_next; @@ -164,9 +164,9 @@ static lzw_result lzw__block_advance(struct lzw_read_ctx *ctx) * \return LZW_OK or LZW_OK_EOD on success, appropriate error otherwise. */ static inline lzw_result lzw__read_code( - struct lzw_read_ctx *ctx, + struct lzw_read_ctx *restrict ctx, uint8_t code_size, - uint32_t *code_out) + uint32_t *restrict code_out) { uint32_t code = 0; uint8_t current_bit = ctx->sb_bit & 0x7; From a373fd766e6d8053e87b44f35f60c16dbea4e2fa Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Sun, 18 Apr 2021 17:31:36 +0100 Subject: [PATCH 40/41] lzw: Always read three bytes on fast path to avoid swtich. --- libvips/foreign/libnsgif/lzw.c | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/libvips/foreign/libnsgif/lzw.c b/libvips/foreign/libnsgif/lzw.c index a3034f8a..4b521b68 100644 --- a/libvips/foreign/libnsgif/lzw.c +++ b/libvips/foreign/libnsgif/lzw.c @@ -41,7 +41,7 @@ struct lzw_read_ctx { uint32_t data_sb_next; /**< Offset to sub-block size */ const uint8_t *sb_data; /**< Pointer to current sub-block in data */ - uint32_t sb_bit; /**< Current bit offset in sub-block */ + size_t sb_bit; /**< Current bit offset in sub-block */ uint32_t sb_bit_count; /**< Bit count in sub-block */ }; @@ -165,29 +165,25 @@ static lzw_result lzw__block_advance(struct lzw_read_ctx *restrict ctx) */ static inline lzw_result lzw__read_code( struct lzw_read_ctx *restrict ctx, - uint8_t code_size, + uint32_t code_size, uint32_t *restrict code_out) { uint32_t code = 0; - uint8_t current_bit = ctx->sb_bit & 0x7; - uint8_t byte_advance = (current_bit + code_size) >> 3; + uint32_t current_bit = ctx->sb_bit & 0x7; - assert(byte_advance <= 2); - - if (ctx->sb_bit + code_size <= ctx->sb_bit_count) { - /* Fast path: code fully inside this sub-block */ + if (ctx->sb_bit + 24 <= ctx->sb_bit_count) { + /* Fast path: read three bytes from this sub-block */ const uint8_t *data = ctx->sb_data + (ctx->sb_bit >> 3); - switch (byte_advance) { - case 2: code |= data[2] << 16; /* Fall through */ - case 1: code |= data[1] << 8; /* Fall through */ - case 0: code |= data[0] << 0; - } + code |= *data++ << 0; + code |= *data++ << 8; + code |= *data << 16; ctx->sb_bit += code_size; } else { /* Slow path: code spans sub-blocks */ + uint8_t byte_advance = (current_bit + code_size) >> 3; uint8_t byte = 0; - uint8_t bits_remaining_0 = (code_size < (8 - current_bit)) ? - code_size : (8 - current_bit); + uint8_t bits_remaining_0 = (code_size < (8u - current_bit)) ? + code_size : (8u - current_bit); uint8_t bits_remaining_1 = code_size - bits_remaining_0; uint8_t bits_used[3] = { [0] = bits_remaining_0, @@ -195,6 +191,8 @@ static inline lzw_result lzw__read_code( [2] = bits_remaining_1 - 8, }; + assert(byte_advance <= 2); + while (true) { const uint8_t *data = ctx->sb_data; lzw_result res; From 4cd5da230314f2009bb15d152845d573330c5e31 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 18 Apr 2021 18:58:09 +0100 Subject: [PATCH 41/41] update libnsgif from upstream --- libvips/foreign/libnsgif/libnsgif.c | 22 ++--- .../libnsgif/patches/delay-alloc.patch | 86 ------------------- 2 files changed, 11 insertions(+), 97 deletions(-) delete mode 100644 libvips/foreign/libnsgif/patches/delay-alloc.patch diff --git a/libvips/foreign/libnsgif/libnsgif.c b/libvips/foreign/libnsgif/libnsgif.c index 382ae33f..429bcd1a 100644 --- a/libvips/foreign/libnsgif/libnsgif.c +++ b/libvips/foreign/libnsgif/libnsgif.c @@ -82,8 +82,8 @@ gif_initialise_sprite(gif_animation *gif, unsigned int width, unsigned int height) { - /* Already allocated? */ - if (gif->frame_image) { + /* Already allocated? */ + if (gif->frame_image) { return GIF_OK; } @@ -378,12 +378,12 @@ static gif_result gif_initialise_frame(gif_animation *gif) gif->frames[frame].redraw_required = ((gif->frames[frame].disposal_method == GIF_FRAME_CLEAR) || (gif->frames[frame].disposal_method == GIF_FRAME_RESTORE)); - /* Frame size may have grown. - */ - gif->width = (offset_x + width > gif->width) ? - offset_x + width : gif->width; - gif->height = (offset_y + height > gif->height) ? - offset_y + height : gif->height; + /* Frame size may have grown. + */ + gif->width = (offset_x + width > gif->width) ? + offset_x + width : gif->width; + gif->height = (offset_y + height > gif->height) ? + offset_y + height : gif->height; /* Decode the flags */ flags = gif_data[9]; @@ -878,9 +878,9 @@ gif_internal_decode_frame(gif_animation *gif, goto gif_decode_frame_exit; } - /* Make sure we have a buffer to decode to. - */ - if (gif_initialise_sprite(gif, gif->width, gif->height)) { + /* Make sure we have a buffer to decode to. + */ + if (gif_initialise_sprite(gif, gif->width, gif->height)) { return GIF_INSUFFICIENT_MEMORY; } diff --git a/libvips/foreign/libnsgif/patches/delay-alloc.patch b/libvips/foreign/libnsgif/patches/delay-alloc.patch deleted file mode 100644 index 21630f2b..00000000 --- a/libvips/foreign/libnsgif/patches/delay-alloc.patch +++ /dev/null @@ -1,86 +0,0 @@ ---- libnsgif-orig.c 2021-04-03 12:23:43.700260070 +0100 -+++ libnsgif.c 2021-04-03 12:24:44.079567702 +0100 -@@ -79,34 +79,17 @@ - unsigned int width, - unsigned int height) - { -- unsigned int max_width; -- unsigned int max_height; -- struct bitmap *buffer; -- -- /* Check if we've changed */ -- if ((width <= gif->width) && (height <= gif->height)) { -+ /* Already allocated? */ -+ if (gif->frame_image) { - return GIF_OK; - } - -- /* Get our maximum values */ -- max_width = (width > gif->width) ? width : gif->width; -- max_height = (height > gif->height) ? height : gif->height; -- -- /* Allocate some more memory */ - assert(gif->bitmap_callbacks.bitmap_create); -- buffer = gif->bitmap_callbacks.bitmap_create(max_width, max_height); -- if (buffer == NULL) { -+ gif->frame_image = gif->bitmap_callbacks.bitmap_create(width, height); -+ if (gif->frame_image == NULL) { - return GIF_INSUFFICIENT_MEMORY; - } - -- assert(gif->bitmap_callbacks.bitmap_destroy); -- gif->bitmap_callbacks.bitmap_destroy(gif->frame_image); -- gif->frame_image = buffer; -- gif->width = max_width; -- gif->height = max_height; -- -- /* Invalidate our currently decoded image */ -- gif->decoded_frame = GIF_INVALID_FRAME; - return GIF_OK; - } - -@@ -392,10 +375,12 @@ - gif->frames[frame].redraw_required = ((gif->frames[frame].disposal_method == GIF_FRAME_CLEAR) || - (gif->frames[frame].disposal_method == GIF_FRAME_RESTORE)); - -- /* Boundary checking - shouldn't ever happen except with junk data */ -- if (gif_initialise_sprite(gif, (offset_x + width), (offset_y + height))) { -- return GIF_INSUFFICIENT_MEMORY; -- } -+ /* Frame size may have grown. -+ */ -+ gif->width = (offset_x + width > gif->width) ? -+ offset_x + width : gif->width; -+ gif->height = (offset_y + height > gif->height) ? -+ offset_y + height : gif->height; - - /* Decode the flags */ - flags = gif_data[9]; -@@ -739,6 +724,12 @@ - goto gif_decode_frame_exit; - } - -+ /* Make sure we have a buffer to decode to. -+ */ -+ if (gif_initialise_sprite(gif, gif->width, gif->height)) { -+ return GIF_INSUFFICIENT_MEMORY; -+ } -+ - /* Decode the flags */ - flags = gif_data[9]; - colour_table_size = 2 << (flags & GIF_COLOUR_TABLE_SIZE_MASK); -@@ -1115,14 +1106,6 @@ - } - gif->frame_holders = 1; - -- /* Initialise the bitmap header */ -- assert(gif->bitmap_callbacks.bitmap_create); -- gif->frame_image = gif->bitmap_callbacks.bitmap_create(gif->width, gif->height); -- if (gif->frame_image == NULL) { -- gif_finalise(gif); -- return GIF_INSUFFICIENT_MEMORY; -- } -- - /* Remember we've done this now */ - gif->buffer_position = gif_data - gif->gif_data; - }