diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c038210e..abc390a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,17 +61,10 @@ jobs: - name: Install macOS dependencies if: contains(matrix.os, 'macos') run: | - brew update - brew install - autoconf automake libtool - gtk-doc gobject-introspection - cfitsio fftw giflib - glib libexif libgsf - libheif libjpeg-turbo libmatio - librsvg libspng libtiff - little-cms2 openexr openslide - orc pango poppler webp - openjpeg + brew update + brew upgrade + brew install autoconf automake libtool fftw fontconfig gtk-doc gobject-introspection glib libexif libgsf little-cms2 orc pango + brew install cfitsio imagemagick@6 libheif libjpeg-turbo libmatio librsvg libspng libtiff openexr openjpeg openslide poppler webp - name: Install Clang 10 env: @@ -96,7 +89,7 @@ jobs: if: contains(matrix.os, 'macos') run: | echo "JOBS=$(sysctl -n hw.logicalcpu)" >> $GITHUB_ENV - echo "PKG_CONFIG_PATH=/usr/local/opt/jpeg-turbo/lib/pkgconfig:/usr/local/opt/libxml2/lib/pkgconfig" >> $GITHUB_ENV + echo "PKG_CONFIG_PATH=/usr/local/opt/jpeg-turbo/lib/pkgconfig:/usr/local/opt/libxml2/lib/pkgconfig:/usr/local/opt/imagemagick@6/lib/pkgconfig" >> $GITHUB_ENV - name: Prepare sanitizers if: matrix.sanitize diff --git a/ChangeLog b/ChangeLog index 606b557f..7875cfdf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -23,6 +23,7 @@ - add GIF load with libnsgif - add JPEG2000 load and save - add JPEG-XL load and save +- add black_point_compensation flag for icc transforms - add "rgba" flag to vips_text() to enable full colour text rendering 22/12/20 start 8.10.6 diff --git a/configure.ac b/configure.ac index b3c5a07c..a49de5de 100644 --- a/configure.ac +++ b/configure.ac @@ -1125,6 +1125,26 @@ fi VIPS_CFLAGS="$VIPS_CFLAGS $PANGOCAIRO_CFLAGS" VIPS_LIBS="$VIPS_LIBS $PANGOCAIRO_LIBS" +# font file support with fontconfig +AC_ARG_WITH([fontconfig], + AS_HELP_STRING([--without-fontconfig], + [build without fontconfig (default: test)])) + +if test x"$with_pangocairo" != x"no" -a x"$with_fontconfig" != x"no"; then + PKG_CHECK_MODULES(FONTCONFIG, fontconfig, + [AC_DEFINE(HAVE_FONTCONFIG,1,[define if you have fontconfig installed.]) + with_fontconfig=yes + PACKAGES_USED="$PACKAGES_USED fontconfig" + ], + [AC_MSG_WARN([fontconfig not found; disabling fontconfig support]) + with_fontconfig=no + ] + ) +fi + +VIPS_CFLAGS="$VIPS_CFLAGS $FONTCONFIG_CFLAGS" +VIPS_LIBS="$VIPS_LIBS $FONTCONFIG_LIBS" + # look for TIFF with pkg-config ... fall back to our tester # pkgconfig support for libtiff starts with libtiff-4 AC_ARG_WITH([tiff], @@ -1348,6 +1368,7 @@ accelerate loops with orc: $with_orc, \ ICC profile support with lcms: $with_lcms, \ zlib: $with_zlib, \ text rendering with pangocairo: $with_pangocairo, \ +font file support with fontconfig: $with_fontconfig, \ EXIF metadata support with libexif: $with_libexif, \ JPEG load/save with libjpeg: $with_jpeg, \ PNG load with libspng: $with_libspng, \ @@ -1450,6 +1471,7 @@ accelerate loops with orc: $with_orc ICC profile support with lcms: $with_lcms zlib: $with_zlib text rendering with pangocairo: $with_pangocairo +font file support with fontconfig: $with_fontconfig EXIF metadata support with libexif: $with_libexif ## File format support diff --git a/libvips/colour/icc_transform.c b/libvips/colour/icc_transform.c index 1caa8a58..6fe7658a 100644 --- a/libvips/colour/icc_transform.c +++ b/libvips/colour/icc_transform.c @@ -43,6 +43,8 @@ * they can be triggered under normal circumstances * 17/4/19 kleisauke * - better rejection of broken embedded profiles + * 29/3/21 [hanssonrickard] + * - add black_point_compensation */ /* @@ -152,6 +154,7 @@ typedef struct _VipsIcc { VipsIntent intent; VipsPCS pcs; int depth; + gboolean black_point_compensation; VipsBlob *in_blob; cmsHPROFILE in_profile; @@ -215,6 +218,8 @@ vips_icc_build( VipsObject *object ) VipsColourCode *code = (VipsColourCode *) object; VipsIcc *icc = (VipsIcc *) object; + cmsUInt32Number flags; + if( icc->depth != 8 && icc->depth != 16 ) { vips_error( class->nickname, @@ -353,10 +358,15 @@ vips_icc_build( VipsObject *object ) /* Use cmsFLAGS_NOCACHE to disable the 1-pixel cache and make * calling cmsDoTransform() from multiple threads safe. */ + flags = cmsFLAGS_NOCACHE; + + if( icc->black_point_compensation ) + flags |= cmsFLAGS_BLACKPOINTCOMPENSATION; + if( !(icc->trans = cmsCreateTransform( icc->in_profile, icc->in_icc_format, icc->out_profile, icc->out_icc_format, - icc->intent, cmsFLAGS_NOCACHE )) ) + icc->intent, flags )) ) return( -1 ); if( VIPS_OBJECT_CLASS( vips_icc_parent_class )-> @@ -394,6 +404,13 @@ vips_icc_class_init( VipsIccClass *class ) G_STRUCT_OFFSET( VipsIcc, pcs ), VIPS_TYPE_PCS, VIPS_PCS_LAB ); + VIPS_ARG_BOOL( class, "black_point_compensation", 7, + _( "Black point compensation" ), + _( "Enable black point compensation" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsIcc, black_point_compensation ), + FALSE ); + cmsSetLogErrorHandler( icc_error ); } @@ -1345,10 +1362,11 @@ vips_icc_is_compatible_profile( VipsImage *image, * * Optional arguments: * - * * @input_profile: get the input profile from here - * * @intent: transform with this intent - * * @embedded: use profile embedded in input image - * * @pcs: use XYZ or LAB PCS + * * @pcs: #VipsPCS, use XYZ or LAB PCS + * * @intent: #VipsIntent, transform with this intent + * * @black_point_compensation: %gboolean, enable black point compensation + * * @embedded: %gboolean, use profile embedded in input image + * * @input_profile: %gchararray, get the input profile from here * * Import an image from device space to D65 LAB with an ICC profile. If @pcs is * set to #VIPS_PCS_XYZ, use CIE XYZ PCS instead. @@ -1365,6 +1383,9 @@ vips_icc_is_compatible_profile( VipsImage *image, * @input_profile. If @input_profile is not supplied, the * metadata profile, if any, is used as a fall-back. * + * If @black_point_compensation is set, LCMS black point compensation is + * enabled. + * * Returns: 0 on success, -1 on error. */ int @@ -1388,10 +1409,11 @@ vips_icc_import( VipsImage *in, VipsImage **out, ... ) * * Optional arguments: * - * * @intent: transform with this intent - * * @depth: depth of output image in bits - * * @output_profile: get the output profile from here - * * @pcs: use XYZ or LAB PCS + * * @pcs: #VipsPCS, use XYZ or LAB PCS + * * @intent: #VipsIntent, transform with this intent + * * @black_point_compensation: %gboolean, enable black point compensation + * * @output_profile: %gchararray, get the output profile from here + * * @depth: %gint, depth of output image in bits * * Export an image from D65 LAB to device space with an ICC profile. * If @pcs is @@ -1400,6 +1422,9 @@ vips_icc_import( VipsImage *in, VipsImage **out, ... ) * If @output_profile is set, export with that and attach it to the output * image. * + * If @black_point_compensation is set, LCMS black point compensation is + * enabled. + * * Returns: 0 on success, -1 on error. */ int @@ -1424,10 +1449,12 @@ vips_icc_export( VipsImage *in, VipsImage **out, ... ) * * Optional arguments: * - * * @input_profile: get the input profile from here - * * @intent: transform with this intent - * * @depth: depth of output image in bits - * * @embedded: use profile embedded in input image + * * @pcs: #VipsPCS, use XYZ or LAB PCS + * * @intent: #VipsIntent, transform with this intent + * * @black_point_compensation: %gboolean, enable black point compensation + * * @embedded: %gboolean, use profile embedded in input image + * * @input_profile: %gchararray, get the input profile from here + * * @depth: %gint, depth of output image in bits * * Transform an image with a pair of ICC profiles. The input image is moved to * profile-connection space with the input profile and then to the output @@ -1445,6 +1472,9 @@ vips_icc_export( VipsImage *in, VipsImage **out, ... ) * @input_profile. If @input_profile is not supplied, the * metadata profile, if any, is used as a fall-back. * + * If @black_point_compensation is set, LCMS black point compensation is + * enabled. + * * The output image has the output profile attached to the #VIPS_META_ICC_NAME * field. * diff --git a/libvips/create/text.c b/libvips/create/text.c index d09f5e5b..34782c4e 100644 --- a/libvips/create/text.c +++ b/libvips/create/text.c @@ -84,7 +84,10 @@ #include #include #include + +#ifdef HAVE_FONTCONFIG #include +#endif #include "pcreate.h" @@ -123,10 +126,12 @@ static GMutex *vips_text_lock = NULL; */ static PangoFontMap *vips_text_fontmap = NULL; +#ifdef HAVE_FONTCONFIG /* All the fontfiles we've loaded. fontconfig lets you add a fontfile * repeatedly, and we obviously don't want that. */ static GHashTable *vips_text_fontfiles = NULL; +#endif static void vips_text_dispose( GObject *gobject ) @@ -365,13 +370,17 @@ vips_text_build( VipsObject *object ) if( !vips_text_fontmap ) vips_text_fontmap = pango_cairo_font_map_new(); + +#ifdef HAVE_FONTCONFIG if( !vips_text_fontfiles ) vips_text_fontfiles = g_hash_table_new( g_str_hash, g_str_equal ); +#endif text->context = pango_font_map_create_context( PANGO_FONT_MAP( vips_text_fontmap ) ); +#ifdef HAVE_FONTCONFIG if( text->fontfile && !g_hash_table_lookup( vips_text_fontfiles, text->fontfile ) ) { if( !FcConfigAppFontAddFile( NULL, @@ -386,6 +395,11 @@ vips_text_build( VipsObject *object ) text->fontfile, g_strdup( text->fontfile ) ); } +#else + if( text->fontfile ) + g_warning( "%s", + _( "ignoring fontfile (no fontconfig support)" ) ); +#endif /* If our caller set height and not dpi, we adjust dpi until * we get a fit. diff --git a/libvips/foreign/libnsgif/libnsgif.c b/libvips/foreign/libnsgif/libnsgif.c index 1c4bd00e..429bcd1a 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 @@ -79,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; } @@ -375,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]; @@ -618,6 +621,160 @@ static gif_result gif__recover_previous_frame(const gif_animation *gif) return GIF_OK; } +static gif_result +gif__decode_complex(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) +{ + unsigned int transparency_index; + uint32_t available = 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; + + 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); + + x = width; + while (x > 0) { + const uint8_t *uncompressed; + unsigned row_available; + if (available == 0) { + 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; + } + res = lzw_decode_continuous(gif->lzw_ctx, + &uncompressed, &available); + } + + row_available = x < available ? x : available; + x -= row_available; + available -= row_available; + while (row_available-- > 0) { + register unsigned int colour; + colour = *uncompressed++; + if (colour != transparency_index) { + *frame_scanline = colour_table[colour]; + } + frame_scanline++; + } + } + } + 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 * @@ -638,11 +795,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) { @@ -724,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; } @@ -796,10 +950,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 +1013,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, diff --git a/libvips/foreign/libnsgif/lzw.c b/libvips/foreign/libnsgif/lzw.c index 1f854964..4b521b68 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 @@ -20,6 +21,8 @@ * Decoder for GIF LZW data. */ +/** Maximum number of lzw table entries. */ +#define LZW_TABLE_ENTRY_MAX (1u << LZW_CODE_MAX) /** * Context for reading LZW data. @@ -33,55 +36,63 @@ * 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 */ 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 */ }; /** - * 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 count; /**< Count of values in this entry's 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 prev_code_count; /**< Total values for 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. */ + + 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[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]; + /** LZW code table. Generated during decode. */ + struct lzw_table_entry table[LZW_TABLE_ENTRY_MAX]; }; @@ -111,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; @@ -152,31 +163,27 @@ 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( - struct lzw_read_ctx *ctx, - uint8_t code_size, - uint32_t *code_out) +static inline lzw_result lzw__read_code( + struct lzw_read_ctx *restrict ctx, + 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, @@ -184,6 +191,8 @@ static inline lzw_result lzw__next_code( [2] = bits_remaining_1 - 8, }; + assert(byte_advance <= 2); + while (true) { const uint8_t *data = ctx->sb_data; lzw_result res; @@ -215,48 +224,18 @@ 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. + * \param[in] ctx LZW reading context, updated. * \return LZW_OK or error code. */ -static lzw_result lzw__clear_codes( - struct lzw_ctx *ctx, - const uint8_t ** const stack_pos_out) +static inline void lzw__clear_table( + struct lzw_ctx *ctx) { - uint32_t code; - 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; - - /* 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); - if (res != LZW_OK) { - return res; - } - } while (code == ctx->clear_code); - - /* The initial code must be from the initial dictionary. */ - 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; - - /* Reset the stack, and add first non-clear code added as first item. */ - stack_pos = ctx->stack_base; - *stack_pos++ = code; - - *stack_pos_out = stack_pos; - return LZW_OK; + /* 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; } @@ -266,13 +245,11 @@ lzw_result lzw_decode_init( const uint8_t *compressed_data, uint32_t compressed_data_len, uint32_t compressed_data_pos, - uint8_t code_size, - const uint8_t ** const stack_base_out, - const uint8_t ** const stack_pos_out) + uint8_t minimum_code_size) { - struct lzw_dictionary_entry *table = ctx->table; + struct lzw_table_entry *table = ctx->table; - if (code_size >= LZW_CODE_MAX) { + if (minimum_code_size >= LZW_CODE_MAX) { return LZW_BAD_ICODE; } @@ -284,98 +261,291 @@ lzw_result lzw_decode_init( ctx->input.sb_bit = 0; ctx->input.sb_bit_count = 0; - /* Initialise the dictionary building context */ - ctx->initial_code_size = code_size; + /* Initialise the table building context */ + 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 */ + ctx->output_left = 0; + + /* 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; + table[i].count = 1; } - *stack_base_out = ctx->stack_base; - return lzw__clear_codes(ctx, stack_pos_out); + lzw__clear_table(ctx); + ctx->prev_code = ctx->clear_code; + + return LZW_OK; } +/** + * Create new table entry. + * + * \param[in] ctx LZW reading context, updated. + * \param[in] code Last value code for new table entry. + */ +static inline void lzw__table_add_entry( + struct lzw_ctx *ctx, + uint32_t code) +{ + struct lzw_table_entry *entry = &ctx->table[ctx->table_size]; -/* Exported function, documented in lzw.h */ -lzw_result lzw_decode(struct lzw_ctx *ctx, - const uint8_t ** const stack_pos_out) + entry->value = code; + entry->first = ctx->prev_code_first; + entry->count = ctx->prev_code_count + 1; + entry->extends = ctx->prev_code; + + ctx->table_size++; +} + +typedef uint32_t (*lzw_writer_fn)( + struct lzw_ctx *ctx, + void *restrict output, + uint32_t length, + uint32_t used, + uint32_t code, + uint32_t left); + +/** + * Get the next LZW code and write its value(s) to output buffer. + * + * \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, + void *restrict output, + uint32_t length, + lzw_writer_fn write_pixels, + uint32_t *restrict used) { 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; - uint32_t current_entry = ctx->current_entry; - struct lzw_dictionary_entry * const table = ctx->table; + 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 == clear_code) { - /* Got Clear code */ - return lzw__clear_codes(ctx, stack_pos_out); - - } else if (code_new == ctx->eoi_code) { + 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; - } else if (code_new < current_entry) { - /* Code is in table */ - code_out = code_new; - last_value = table[code_new].first_value; + } else if (code == ctx->clear_code) { + lzw__clear_table(ctx); } else { - /* Code not in table */ - *stack_pos++ = ctx->previous_code_first; - code_out = ctx->previous_code; - last_value = ctx->previous_code_first; - } + 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); - /* Add to the dictionary, only if there's space */ - if (current_entry < (1 << LZW_CODE_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++; - } - - /* Ensure code size is increased, if needed. */ - if (current_entry == ctx->current_code_size_max) { - if (ctx->current_code_size < LZW_CODE_MAX) { - ctx->current_code_size++; - ctx->current_code_size_max = - (1 << ctx->current_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; + } } + + *used += write_pixels(ctx, output, length, *used, code, + ctx->table[code].count); } /* Store details of this code as "previous code" to the context. */ - ctx->previous_code_first = table[code_new].first_value; - ctx->previous_code = code_new; + ctx->prev_code_first = ctx->table[code].first; + ctx->prev_code_count = ctx->table[code].count; + ctx->prev_code = code; + + 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; + const 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--) { + const struct lzw_table_entry *entry = table + code; + code = entry->extends; + } + + output_pos += count; + for (unsigned i = count; i != 0; i--) { + const 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, + uint32_t *restrict used) +{ + *used = 0; + *data = ctx->stack_base; + 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; +} + +/** + * 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; + const 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--) { + const struct lzw_table_entry *entry = table + code; + code = entry->extends; + } + + stack_pos += count; + for (unsigned i = count; i != 0; i--) { + const 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; + } + } - /* 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; - *stack_pos++ = entry->last_value; - code_out = entry->previous_entry; - } - *stack_pos++ = table[code_out].last_value; - - *stack_pos_out = stack_pos; return LZW_OK; } diff --git a/libvips/foreign/libnsgif/lzw.h b/libvips/foreign/libnsgif/lzw.h index 385b4255..c442cff6 100644 --- a/libvips/foreign/libnsgif/lzw.h +++ b/libvips/foreign/libnsgif/lzw.h @@ -65,11 +65,8 @@ 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` - * current stack entries. * \return LZW_OK on success, or appropriate error code otherwise. */ lzw_result lzw_decode_init( @@ -77,29 +74,61 @@ lzw_result lzw_decode_init( const uint8_t *compressed_data, uint32_t compressed_data_len, uint32_t compressed_data_pos, - uint8_t code_size, - const uint8_t ** const stack_base_out, - const uint8_t ** const stack_pos_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. * - * 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] 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, - const uint8_t ** const stack_pos_out); +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); + +/** + * 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 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; - }