diff --git a/ChangeLog b/ChangeLog index a1edfb96..7875cfdf 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 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 diff --git a/README.md b/README.md index 45c05f9e..9b3273fa 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,11 @@ 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, 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. +It supports a good range of image formats, including JPEG, JPEG2000, JPEG-XL, +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. It comes with bindings for [C](https://libvips.github.io/libvips/API/current/using-from-c.html), @@ -47,7 +48,7 @@ Rails](https://edgeguides.rubyonrails.org/active_storage_overview.html), [mediawiki](https://www.mediawiki.org/wiki/Extension:VipsScaler), [PhotoFlow](https://github.com/aferrero2707/PhotoFlow) and others. The official libvips GUI is [nip2](https://github.com/libvips/nip2), -a strange combination of a spreadsheet and an photo editor. +a strange combination of a spreadsheet and a photo editor. # Install @@ -278,6 +279,10 @@ OpenEXR images. If available, libvips will read and write JPEG2000 images. +### libjxl + +If available, libvips will read and write JPEG-XL images. + ### OpenSlide If available, libvips can load OpenSlide-supported virtual slide diff --git a/configure.ac b/configure.ac index 36696e24..1b92a872 100644 --- a/configure.ac +++ b/configure.ac @@ -809,6 +809,28 @@ VIPS_CFLAGS="$VIPS_CFLAGS $NIFTI_CFLAGS" VIPS_INCLUDES="$VIPS_INCLUDES $NIFTI_INCLUDES" VIPS_LIBS="$VIPS_LIBS $NIFTI_LIBS" +# jpeg-xl +AC_ARG_WITH([libjxl], + AS_HELP_STRING([--without-libjxl], + [build without libjxl (default: test)])) + +if test x"$with_libjxl" != x"no"; then + PKG_CHECK_MODULES(LIBJXL, libjxl_threads >= 0.3.7 libjxl >= 0.3.7, + [AC_DEFINE(HAVE_LIBJXL,1, + [define if you have libjxl >= 0.3.7 installed.]) + with_libjxl=yes + PACKAGES_USED="$PACKAGES_USED libjxl" + ], + [AC_MSG_WARN([libjxl not found; disabling libjxl support]) + with_libjxl=no + ] + ) +fi + +VIPS_CFLAGS="$VIPS_CFLAGS $LIBJXL_CFLAGS" +VIPS_INCLUDES="$VIPS_INCLUDES $LIBJXL_INCLUDES" +VIPS_LIBS="$VIPS_LIBS $LIBJXL_LIBS" + # openjpeg AC_ARG_WITH([libopenjp2], AS_HELP_STRING([--without-libopenjp2], @@ -1531,6 +1553,9 @@ EXIF metadata support with libexif: $with_libexif ## File format support JPEG load/save with libjpeg: $with_jpeg +JXL load/save with libjxl: $with_libjxl +JPEG2000 load/save with libopenjp2: $with_libopenjp2 + (requires libopenjp2 2.2 or later) PNG load with libspng: $with_libspng (requires libspng-0.6 or later) PNG load/save with libpng: $with_png diff --git a/libvips/create/text.c b/libvips/create/text.c index 34782c4e..fe27d10f 100644 --- a/libvips/create/text.c +++ b/libvips/create/text.c @@ -126,12 +126,10 @@ 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 ) @@ -371,11 +369,9 @@ 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 ) ); @@ -383,23 +379,22 @@ vips_text_build( VipsObject *object ) #ifdef HAVE_FONTCONFIG if( text->fontfile && !g_hash_table_lookup( vips_text_fontfiles, text->fontfile ) ) { + /* This can fail if you eg. add the same font from two + * different files. Just warn. + */ if( !FcConfigAppFontAddFile( NULL, - (const FcChar8 *) text->fontfile ) ) { - vips_error( class->nickname, - _( "unable to load font \"%s\"" ), + (const FcChar8 *) text->fontfile ) ) + g_warning( _( "unable to load fontfile \"%s\"" ), text->fontfile ); - g_mutex_unlock( vips_text_lock ); - return( -1 ); - } g_hash_table_insert( vips_text_fontfiles, text->fontfile, g_strdup( text->fontfile ) ); } -#else +#else /*!HAVE_FONTCONFIG*/ if( text->fontfile ) g_warning( "%s", _( "ignoring fontfile (no fontconfig support)" ) ); -#endif +#endif /*HAVE_FONTCONFIG*/ /* If our caller set height and not dpi, we adjust dpi until * we get a fit. diff --git a/libvips/foreign/Makefile.am b/libvips/foreign/Makefile.am index 84581272..403a476a 100644 --- a/libvips/foreign/Makefile.am +++ b/libvips/foreign/Makefile.am @@ -19,6 +19,8 @@ libforeign_la_SOURCES = \ foreign.c \ heifload.c \ heifsave.c \ + jxlload.c \ + jxlsave.c \ jp2kload.c \ jp2ksave.c \ jpeg2vips.c \ diff --git a/libvips/foreign/dzsave.c b/libvips/foreign/dzsave.c index d5d1d2b9..865680d7 100644 --- a/libvips/foreign/dzsave.c +++ b/libvips/foreign/dzsave.c @@ -366,7 +366,8 @@ vips_gsf_path( VipsGsfDirectory *tree, const char *name, ... ) * path we are creating. */ tree->file_count += 1; - tree->filename_lengths += strlen( tree->out->name ) + strlen( name ) + 1; + tree->filename_lengths += + strlen( tree->out->name ) + strlen( name ) + 1; dir = tree; va_start( ap, name ); diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index 2e0d7c39..068f52f4 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -1396,7 +1396,7 @@ vips__foreign_convert_saveable( VipsImage *in, VipsImage **ready, } /* If this is something other than CMYK or RAD, and it's not already - * an RGB image, eg. maybe a LAB or scRGB image, we need to transform + * an RGB image, eg. maybe a LAB image, we need to transform * to RGB. */ if( !coding[VIPS_CODING_RAD] && @@ -1404,6 +1404,7 @@ vips__foreign_convert_saveable( VipsImage *in, VipsImage **ready, in->Type != VIPS_INTERPRETATION_CMYK && in->Type != VIPS_INTERPRETATION_sRGB && in->Type != VIPS_INTERPRETATION_RGB16 && + in->Type != VIPS_INTERPRETATION_scRGB && vips_colourspace_issupported( in ) && (saveable == VIPS_SAVEABLE_RGB || saveable == VIPS_SAVEABLE_RGBA || @@ -2183,6 +2184,13 @@ vips_foreign_operation_init( void ) extern GType vips_foreign_save_jp2k_buffer_get_type( void ); extern GType vips_foreign_save_jp2k_target_get_type( void ); + extern GType vips_foreign_load_jxl_file_get_type( void ); + extern GType vips_foreign_load_jxl_buffer_get_type( void ); + extern GType vips_foreign_load_jxl_source_get_type( void ); + extern GType vips_foreign_save_jxl_file_get_type( void ); + extern GType vips_foreign_save_jxl_buffer_get_type( void ); + extern GType vips_foreign_save_jxl_target_get_type( void ); + extern GType vips_foreign_load_heif_file_get_type( void ); extern GType vips_foreign_load_heif_buffer_get_type( void ); extern GType vips_foreign_load_heif_source_get_type( void ); @@ -2256,6 +2264,15 @@ vips_foreign_operation_init( void ) vips_foreign_load_svg_source_get_type(); #endif /*HAVE_RSVG*/ +#ifdef HAVE_LIBJXL + vips_foreign_load_jxl_file_get_type(); + vips_foreign_load_jxl_buffer_get_type(); + vips_foreign_load_jxl_source_get_type(); + vips_foreign_save_jxl_file_get_type(); + vips_foreign_save_jxl_buffer_get_type(); + vips_foreign_save_jxl_target_get_type(); +#endif /*HAVE_LIBJXL*/ + #ifdef HAVE_LIBOPENJP2 vips_foreign_load_jp2k_file_get_type(); vips_foreign_load_jp2k_buffer_get_type(); diff --git a/libvips/foreign/jp2kload.c b/libvips/foreign/jp2kload.c index 842574c3..f320aaba 100644 --- a/libvips/foreign/jp2kload.c +++ b/libvips/foreign/jp2kload.c @@ -274,6 +274,10 @@ vips_foreign_load_jp2k_error_callback( const char *msg, void *client ) vips_error( class->nickname, "%s", msg ); jp2k->n_errors += 1; + +#ifdef DEBUG + printf( "%s: error %s", class->nickname, msg ); +#endif /*DEBUG*/ } /* The openjpeg info and warning callbacks are incredibly chatty. @@ -862,7 +866,7 @@ vips_foreign_load_jp2k_load( VipsForeignLoad *load ) if( vips_tilecache( t[0], &t[1], "tile_width", tile_width, "tile_height", tile_height, - "max_tiles", (int) (2.5 * tiles_across), + "max_tiles", 3 * tiles_across, NULL ) ) return( -1 ); if( vips_image_write( t[1], load->real ) ) diff --git a/libvips/foreign/jp2ksave.c b/libvips/foreign/jp2ksave.c index 961bdec0..866f0b1c 100644 --- a/libvips/foreign/jp2ksave.c +++ b/libvips/foreign/jp2ksave.c @@ -1113,7 +1113,7 @@ vips_jp2ksave( VipsImage *in, const char *filename, ... ) * * @tile_height: %gint for tile size * * @subsample_mode: #VipsForeignSubsample, chroma subsampling mode * - * As vips_jp2ksave(), but save to a target. + * As vips_jp2ksave(), but save to a memory buffer. * * See also: vips_jp2ksave(), vips_image_write_to_target(). * diff --git a/libvips/foreign/jxlload.c b/libvips/foreign/jxlload.c new file mode 100644 index 00000000..7dca7e2e --- /dev/null +++ b/libvips/foreign/jxlload.c @@ -0,0 +1,973 @@ +/* load jpeg-xl + * + * 18/3/20 + * - from heifload.c + */ + +/* + + This file is part of VIPS. + + VIPS is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +/* +#define DEBUG_VERBOSE +#define DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include + +#include +#include +#include + +#ifdef HAVE_LIBJXL + +#include +#include + +#include "pforeign.h" + +/* TODO: + * + * - add metadata support + * + * - add animation support + * + * - add "shrink" option to read out 8x shrunk image? + * + * - fix scRGB gamma + */ + +#define INPUT_BUFFER_SIZE (4096) + +typedef struct _VipsForeignLoadJxl { + VipsForeignLoad parent_object; + + /* Source to load from (set by subclasses). + */ + VipsSource *source; + + /* Page set by user, then we translate that into shrink factor. + */ + int page; + int shrink; + + /* Base image properties. + */ + JxlBasicInfo info; + JxlPixelFormat format; + size_t icc_size; + uint8_t *icc_data; + + /* Decompress state. + */ + void *runner; + JxlDecoder *decoder; + + /* Our input buffer. + */ + uint8_t input_buffer[INPUT_BUFFER_SIZE]; + size_t bytes_in_buffer; + + /* Number of errors reported during load -- use this to block load of + * corrupted images. + */ + int n_errors; + + /* If we need to upsample tiles read from opj. + */ + gboolean upsample; + + /* If we need to do ycc->rgb conversion on load. + */ + gboolean ycc_to_rgb; +} VipsForeignLoadJxl; + +typedef VipsForeignLoadClass VipsForeignLoadJxlClass; + +G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadJxl, vips_foreign_load_jxl, + VIPS_TYPE_FOREIGN_LOAD ); + +static void +vips_foreign_load_jxl_dispose( GObject *gobject ) +{ + VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) gobject; + +#ifdef DEBUG + printf( "vips_foreign_load_jxl_dispose:\n" ); +#endif /*DEBUG*/ + + VIPS_FREEF( JxlThreadParallelRunnerDestroy, jxl->runner ); + VIPS_FREEF( JxlDecoderDestroy, jxl->decoder ); + VIPS_FREE( jxl->icc_data ); + + G_OBJECT_CLASS( vips_foreign_load_jxl_parent_class )-> + dispose( gobject ); +} + +static void +vips_foreign_load_jxl_error( VipsForeignLoadJxl *jxl, const char *details ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( jxl ); + + /* TODO ... jxl has no way to get error messages at the moment. + */ + vips_error( class->nickname, "error %s", details ); +} + +static int +vips_foreign_load_jxl_build( VipsObject *object ) +{ + VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) object; + +#ifdef DEBUG + printf( "vips_foreign_load_jxl_build:\n" ); +#endif /*DEBUG*/ + + jxl->runner = JxlThreadParallelRunnerCreate( NULL, + vips_concurrency_get() ); + jxl->decoder = JxlDecoderCreate( NULL ); + + if( JxlDecoderSubscribeEvents( jxl->decoder, + JXL_DEC_COLOR_ENCODING | + JXL_DEC_BASIC_INFO | + JXL_DEC_FULL_IMAGE ) ) { + vips_foreign_load_jxl_error( jxl, "JxlDecoderSubscribeEvents" ); + return( -1 ); + } + if( JxlDecoderSetParallelRunner( jxl->decoder, + JxlThreadParallelRunner, jxl->runner ) ) { + vips_foreign_load_jxl_error( jxl, + "JxlDecoderSetParallelRunner" ); + return( -1 ); + } + + if( VIPS_OBJECT_CLASS( vips_foreign_load_jxl_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static gboolean +vips_foreign_load_jxl_is_a_source( VipsSource *source ) +{ + const unsigned char *p; + JxlSignature sig; + + return( (p = vips_source_sniff( source, 12 )) && + (sig = JxlSignatureCheck( p, 12 )) == JXL_SIG_CODESTREAM ); +} + +static VipsForeignFlags +vips_foreign_load_jxl_get_flags( VipsForeignLoad *load ) +{ + return( VIPS_FOREIGN_PARTIAL ); +} + +static int +vips_foreign_load_jxl_fill_input( VipsForeignLoadJxl *jxl, + size_t bytes_remaining ) +{ + gint64 bytes_read; + + memcpy( jxl->input_buffer, + jxl->input_buffer + jxl->bytes_in_buffer - bytes_remaining, + bytes_remaining ); + bytes_read = vips_source_read( jxl->source, + jxl->input_buffer + bytes_remaining, + INPUT_BUFFER_SIZE - bytes_remaining ); + if( bytes_read < 0 ) + return( -1 ); + jxl->bytes_in_buffer = bytes_read + bytes_remaining; + + return( 0 ); +} + +#ifdef DEBUG +static void +vips_foreign_load_jxl_print_status( JxlDecoderStatus status ) +{ + switch( status ) { + case JXL_DEC_SUCCESS: + printf( "JXL_DEC_SUCCESS\n" ); + break; + + case JXL_DEC_ERROR: + printf( "JXL_DEC_ERROR\n" ); + break; + + case JXL_DEC_NEED_MORE_INPUT: + printf( "JXL_DEC_NEED_MORE_INPUT\n" ); + break; + + case JXL_DEC_NEED_PREVIEW_OUT_BUFFER: + printf( "JXL_DEC_NEED_PREVIEW_OUT_BUFFER\n" ); + break; + + case JXL_DEC_NEED_DC_OUT_BUFFER: + printf( "JXL_DEC_NEED_DC_OUT_BUFFER\n" ); + break; + + case JXL_DEC_NEED_IMAGE_OUT_BUFFER: + printf( "JXL_DEC_NEED_IMAGE_OUT_BUFFER\n" ); + break; + + case JXL_DEC_JPEG_NEED_MORE_OUTPUT: + printf( "JXL_DEC_JPEG_NEED_MORE_OUTPUT\n" ); + break; + + case JXL_DEC_BASIC_INFO: + printf( "JXL_DEC_BASIC_INFO\n" ); + break; + + case JXL_DEC_EXTENSIONS: + printf( "JXL_DEC_EXTENSIONS\n" ); + break; + + case JXL_DEC_COLOR_ENCODING: + printf( "JXL_DEC_COLOR_ENCODING\n" ); + break; + + case JXL_DEC_PREVIEW_IMAGE: + printf( "JXL_DEC_PREVIEW_IMAGE\n" ); + break; + + case JXL_DEC_FRAME: + printf( "JXL_DEC_FRAME\n" ); + break; + + case JXL_DEC_DC_IMAGE: + printf( "JXL_DEC_DC_IMAGE\n" ); + break; + + case JXL_DEC_FULL_IMAGE: + printf( "JXL_DEC_FULL_IMAGE\n" ); + break; + + case JXL_DEC_JPEG_RECONSTRUCTION: + printf( "JXL_DEC_JPEG_RECONSTRUCTION\n" ); + break; + + default: + printf( "JXL_DEC_\n" ); + break; + } +} + +static void +vips_foreign_load_jxl_print_info( JxlBasicInfo *info ) +{ + printf( "JxlBasicInfo:\n" ); + printf( " have_container = %d\n", info->have_container ); + printf( " xsize = %d\n", info->xsize ); + printf( " ysize = %d\n", info->ysize ); + printf( " bits_per_sample = %d\n", info->bits_per_sample ); + printf( " exponent_bits_per_sample = %d\n", + info->exponent_bits_per_sample ); + printf( " intensity_target = %g\n", info->intensity_target ); + printf( " min_nits = %g\n", info->min_nits ); + printf( " relative_to_max_display = %d\n", + info->relative_to_max_display ); + printf( " linear_below = %g\n", info->linear_below ); + printf( " uses_original_profile = %d\n", + info->uses_original_profile ); + printf( " have_preview = %d\n", info->have_preview ); + printf( " have_animation = %d\n", info->have_animation ); + printf( " orientation = %d\n", info->orientation ); + printf( " num_color_channels = %d\n", info->num_color_channels ); + printf( " num_extra_channels = %d\n", info->num_extra_channels ); + printf( " alpha_bits = %d\n", info->alpha_bits ); + printf( " alpha_exponent_bits = %d\n", info->alpha_exponent_bits ); + printf( " alpha_premultiplied = %d\n", info->alpha_premultiplied ); + printf( " preview.xsize = %d\n", info->preview.xsize ); + printf( " preview.ysize = %d\n", info->preview.ysize ); + printf( " animation.tps_numerator = %d\n", + info->animation.tps_numerator ); + printf( " animation.tps_denominator = %d\n", + info->animation.tps_denominator ); + printf( " animation.num_loops = %d\n", info->animation.num_loops ); + printf( " animation.have_timecodes = %d\n", + info->animation.have_timecodes ); +} + +static void +vips_foreign_load_jxl_print_format( JxlPixelFormat *format ) +{ + printf( "JxlPixelFormat:\n" ); + printf( " data_type = " ); + switch( format->data_type ) { + case JXL_TYPE_UINT8: + printf( "JXL_TYPE_UINT8" ); + break; + + case JXL_TYPE_UINT16: + printf( "JXL_TYPE_UINT16" ); + break; + + case JXL_TYPE_UINT32: + printf( "JXL_TYPE_UINT32" ); + break; + + case JXL_TYPE_FLOAT: + printf( "JXL_TYPE_FLOAT" ); + break; + + default: + printf( "(unknown)" ); + break; + } + printf( "\n" ); + printf( " num_channels = %d\n", format->num_channels ); + printf( " endianness = %d\n", format->endianness ); + printf( " align = %zd\n", format->align ); +} +#endif /*DEBUG*/ + +static JxlDecoderStatus +vips_foreign_load_jxl_process( VipsForeignLoadJxl *jxl ) +{ + JxlDecoderStatus status; + + while( (status = JxlDecoderProcessInput( jxl->decoder )) == + JXL_DEC_NEED_MORE_INPUT ) { + size_t bytes_remaining; + + bytes_remaining = JxlDecoderReleaseInput( jxl->decoder ); + if( vips_foreign_load_jxl_fill_input( jxl, bytes_remaining ) && + bytes_remaining == 0 ) + return( JXL_DEC_ERROR ); + JxlDecoderSetInput( jxl->decoder, + jxl->input_buffer, jxl->bytes_in_buffer ); + } + +#ifdef DEBUG + printf( "vips_foreign_load_jxl_process: seen " ); + vips_foreign_load_jxl_print_status( status ); +#endif /*DEBUG*/ + + return( status ); +} + +static int +vips_foreign_load_jxl_set_header( VipsForeignLoadJxl *jxl, VipsImage *out ) +{ + VipsBandFormat format; + VipsInterpretation interpretation; + + switch( jxl->format.data_type ) { + case JXL_TYPE_UINT8: + format = VIPS_FORMAT_UCHAR; + break; + + case JXL_TYPE_UINT16: + format = VIPS_FORMAT_USHORT; + break; + + case JXL_TYPE_UINT32: + format = VIPS_FORMAT_UINT; + break; + + case JXL_TYPE_FLOAT: + format = VIPS_FORMAT_FLOAT; + break; + + default: + g_assert_not_reached(); + } + + switch( jxl->info.num_color_channels ) { + case 1: + switch( format ) { + case VIPS_FORMAT_UCHAR: + interpretation = VIPS_INTERPRETATION_B_W; + break; + + case VIPS_FORMAT_USHORT: + case VIPS_FORMAT_UINT: + interpretation = VIPS_INTERPRETATION_GREY16; + break; + + default: + interpretation = VIPS_INTERPRETATION_B_W; + break; + } + break; + + case 3: + switch( format ) { + case VIPS_FORMAT_UCHAR: + interpretation = VIPS_INTERPRETATION_sRGB; + break; + + case VIPS_FORMAT_USHORT: + case VIPS_FORMAT_UINT: + interpretation = VIPS_INTERPRETATION_RGB16; + break; + + case VIPS_FORMAT_FLOAT: + interpretation = VIPS_INTERPRETATION_scRGB; + break; + + default: + interpretation = VIPS_INTERPRETATION_sRGB; + break; + } + break; + + default: + interpretation = VIPS_INTERPRETATION_MULTIBAND; + break; + } + + /* Even though this is a full image reader, we hint thinstrip since + * we are quite happy serving that if anything downstream + * would like it. + */ + vips_image_pipelinev( out, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); + + vips_image_init_fields( out, + jxl->info.xsize, jxl->info.ysize, jxl->format.num_channels, + format, VIPS_CODING_NONE, interpretation, 1.0, 1.0 ); + + if( jxl->icc_data && + jxl->icc_size > 0 ) { + vips_image_set_blob( out, VIPS_META_ICC_NAME, + (VipsCallbackFn) vips_area_free_cb, + jxl->icc_data, jxl->icc_size ); + jxl->icc_data = NULL; + jxl->icc_size = 0; + } + + vips_image_set_int( out, + VIPS_META_ORIENTATION, jxl->info.orientation ); + + return( 0 ); +} + +static int +vips_foreign_load_jxl_header( VipsForeignLoad *load ) +{ + VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) load; + + JxlDecoderStatus status; + +#ifdef DEBUG + printf( "vips_foreign_load_jxl_header:\n" ); +#endif /*DEBUG*/ + + if( vips_foreign_load_jxl_fill_input( jxl, 0 ) ) + return( -1 ); + JxlDecoderSetInput( jxl->decoder, + jxl->input_buffer, jxl->bytes_in_buffer ); + + /* Read to the end of the header. + */ + do { + switch( (status = vips_foreign_load_jxl_process( jxl )) ) { + case JXL_DEC_ERROR: + vips_foreign_load_jxl_error( jxl, + "JxlDecoderProcessInput" ); + return( -1 ); + + case JXL_DEC_BASIC_INFO: + if( JxlDecoderGetBasicInfo( jxl->decoder, + &jxl->info ) ) { + vips_foreign_load_jxl_error( jxl, + "JxlDecoderGetBasicInfo" ); + return( -1 ); + } +#ifdef DEBUG + vips_foreign_load_jxl_print_info( &jxl->info ); +#endif /*DEBUG*/ + + /* Pick a pixel format to decode to. + */ + jxl->format.num_channels = + jxl->info.num_color_channels + + jxl->info.num_extra_channels; + if( jxl->info.exponent_bits_per_sample > 0 || + jxl->info.alpha_exponent_bits > 0 ) + jxl->format.data_type = JXL_TYPE_FLOAT; + else if( jxl->info.bits_per_sample > 16 ) + jxl->format.data_type = JXL_TYPE_UINT32; + else if( jxl->info.bits_per_sample > 8 ) + jxl->format.data_type = JXL_TYPE_UINT16; + else + jxl->format.data_type = JXL_TYPE_UINT8; + jxl->format.endianness = JXL_NATIVE_ENDIAN; + jxl->format.align = 0; + +#ifdef DEBUG + vips_foreign_load_jxl_print_format( &jxl->format ); +#endif /*DEBUG*/ + + break; + + case JXL_DEC_COLOR_ENCODING: + if( JxlDecoderGetICCProfileSize( jxl->decoder, + &jxl->format, + JXL_COLOR_PROFILE_TARGET_DATA, + &jxl->icc_size ) ) { + vips_foreign_load_jxl_error( jxl, + "JxlDecoderGetICCProfileSize" ); + return( -1 ); + } + if( !(jxl->icc_data = vips_malloc( NULL, + jxl->icc_size )) ) + return( -1 ); + + if( JxlDecoderGetColorAsICCProfile( jxl->decoder, + &jxl->format, + JXL_COLOR_PROFILE_TARGET_DATA, + jxl->icc_data, jxl->icc_size ) ) { + vips_foreign_load_jxl_error( jxl, + "JxlDecoderGetColorAsICCProfile" ); + return( -1 ); + } + break; + + default: + break; + } + } while( status != JXL_DEC_NEED_IMAGE_OUT_BUFFER ); + + if( vips_foreign_load_jxl_set_header( jxl, load->out ) ) + return( -1 ); + + VIPS_SETSTR( load->out->filename, + vips_connection_filename( VIPS_CONNECTION( jxl->source ) ) ); + + return( 0 ); +} + +static int +vips_foreign_load_jxl_load( VipsForeignLoad *load ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load ); + VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) load; + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( load ), 3 ); + + size_t buffer_size; + JxlDecoderStatus status; + +#ifdef DEBUG + printf( "vips_foreign_load_jxl_load:\n" ); +#endif /*DEBUG*/ + + t[0] = vips_image_new(); + if( vips_foreign_load_jxl_set_header( jxl, t[0] ) ) + return( -1 ); + + /* Read to the end of the image. + */ + do { + switch( (status = vips_foreign_load_jxl_process( jxl )) ) { + case JXL_DEC_ERROR: + vips_foreign_load_jxl_error( jxl, + "JxlDecoderProcessInput" ); + return( -1 ); + + case JXL_DEC_NEED_IMAGE_OUT_BUFFER: + if( vips_image_write_prepare( t[0] ) ) + return( -1 ); + + if( JxlDecoderImageOutBufferSize( jxl->decoder, + &jxl->format, + &buffer_size ) ) { + vips_foreign_load_jxl_error( jxl, + "JxlDecoderImageOutBufferSize" ); + return( -1 ); + } + if( buffer_size != + VIPS_IMAGE_SIZEOF_IMAGE( t[0] ) ) { + vips_error( class->nickname, + "%s", _( "bad buffer size" ) ); + return( -1 ); + } + if( JxlDecoderSetImageOutBuffer( jxl->decoder, + &jxl->format, + VIPS_IMAGE_ADDR( t[0], 0, 0 ), + VIPS_IMAGE_SIZEOF_IMAGE( t[0] ) ) ) { + vips_foreign_load_jxl_error( jxl, + "JxlDecoderSetImageOutBuffer" ); + return( -1 ); + } + break; + + case JXL_DEC_FULL_IMAGE: + /* Image decoded. + */ + break; + + default: + break; + } + } while( status != JXL_DEC_SUCCESS ); + + if( vips_image_write( t[0], load->real ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_load_jxl_class_init( VipsForeignLoadJxlClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + + gobject_class->dispose = vips_foreign_load_jxl_dispose; + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "jxlload_base"; + object_class->description = _( "load JPEG-XL image" ); + object_class->build = vips_foreign_load_jxl_build; + + load_class->get_flags = vips_foreign_load_jxl_get_flags; + load_class->header = vips_foreign_load_jxl_header; + load_class->load = vips_foreign_load_jxl_load; + +} + +static void +vips_foreign_load_jxl_init( VipsForeignLoadJxl *jxl ) +{ +} + +typedef struct _VipsForeignLoadJxlFile { + VipsForeignLoadJxl parent_object; + + /* Filename for load. + */ + char *filename; + +} VipsForeignLoadJxlFile; + +typedef VipsForeignLoadJxlClass VipsForeignLoadJxlFileClass; + +G_DEFINE_TYPE( VipsForeignLoadJxlFile, vips_foreign_load_jxl_file, + vips_foreign_load_jxl_get_type() ); + +static int +vips_foreign_load_jxl_file_build( VipsObject *object ) +{ + VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) object; + VipsForeignLoadJxlFile *file = (VipsForeignLoadJxlFile *) object; + + if( file->filename && + !(jxl->source = vips_source_new_from_file( file->filename )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_jxl_file_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +const char *vips__jxl_suffs[] = + { ".jxl", NULL }; + +static int +vips_foreign_load_jxl_is_a( const char *filename ) +{ + VipsSource *source; + gboolean result; + + if( !(source = vips_source_new_from_file( filename )) ) + return( FALSE ); + result = vips_foreign_load_jxl_is_a_source( source ); + VIPS_UNREF( source ); + + return( result ); +} + +static void +vips_foreign_load_jxl_file_class_init( VipsForeignLoadJxlFileClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignClass *foreign_class = (VipsForeignClass *) class; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "jxlload"; + object_class->build = vips_foreign_load_jxl_file_build; + + foreign_class->suffs = vips__jxl_suffs; + + load_class->is_a = vips_foreign_load_jxl_is_a; + + VIPS_ARG_STRING( class, "filename", 1, + _( "Filename" ), + _( "Filename to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadJxlFile, filename ), + NULL ); + +} + +static void +vips_foreign_load_jxl_file_init( VipsForeignLoadJxlFile *jxl ) +{ +} + +typedef struct _VipsForeignLoadJxlBuffer { + VipsForeignLoadJxl parent_object; + + /* Load from a buffer. + */ + VipsArea *buf; + +} VipsForeignLoadJxlBuffer; + +typedef VipsForeignLoadJxlClass VipsForeignLoadJxlBufferClass; + +G_DEFINE_TYPE( VipsForeignLoadJxlBuffer, vips_foreign_load_jxl_buffer, + vips_foreign_load_jxl_get_type() ); + +static int +vips_foreign_load_jxl_buffer_build( VipsObject *object ) +{ + VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) object; + VipsForeignLoadJxlBuffer *buffer = + (VipsForeignLoadJxlBuffer *) object; + + if( buffer->buf ) + if( !(jxl->source = vips_source_new_from_memory( + VIPS_AREA( buffer->buf )->data, + VIPS_AREA( buffer->buf )->length )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_jxl_file_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static gboolean +vips_foreign_load_jxl_buffer_is_a( const void *buf, size_t len ) +{ + VipsSource *source; + gboolean result; + + if( !(source = vips_source_new_from_memory( buf, len )) ) + return( FALSE ); + result = vips_foreign_load_jxl_is_a_source( source ); + VIPS_UNREF( source ); + + return( result ); +} + +static void +vips_foreign_load_jxl_buffer_class_init( VipsForeignLoadJxlBufferClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "jxlload_buffer"; + object_class->build = vips_foreign_load_jxl_buffer_build; + + load_class->is_a_buffer = vips_foreign_load_jxl_buffer_is_a; + + VIPS_ARG_BOXED( class, "buffer", 1, + _( "Buffer" ), + _( "Buffer to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadJxlBuffer, buf ), + VIPS_TYPE_BLOB ); + +} + +static void +vips_foreign_load_jxl_buffer_init( VipsForeignLoadJxlBuffer *buffer ) +{ +} + +typedef struct _VipsForeignLoadJxlSource { + VipsForeignLoadJxl parent_object; + + /* Load from a source. + */ + VipsSource *source; + +} VipsForeignLoadJxlSource; + +typedef VipsForeignLoadJxlClass VipsForeignLoadJxlSourceClass; + +G_DEFINE_TYPE( VipsForeignLoadJxlSource, vips_foreign_load_jxl_source, + vips_foreign_load_jxl_get_type() ); + +static int +vips_foreign_load_jxl_source_build( VipsObject *object ) +{ + VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) object; + VipsForeignLoadJxlSource *source = + (VipsForeignLoadJxlSource *) object; + + if( source->source ) { + jxl->source = source->source; + g_object_ref( jxl->source ); + } + + if( VIPS_OBJECT_CLASS( + vips_foreign_load_jxl_source_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_load_jxl_source_class_init( VipsForeignLoadJxlSourceClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "jxlload_source"; + object_class->build = vips_foreign_load_jxl_source_build; + + load_class->is_a_source = vips_foreign_load_jxl_is_a_source; + + VIPS_ARG_OBJECT( class, "source", 1, + _( "Source" ), + _( "Source to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadJxlSource, source ), + VIPS_TYPE_SOURCE ); + +} + +static void +vips_foreign_load_jxl_source_init( + VipsForeignLoadJxlSource *jxl ) +{ +} + +#endif /*HAVE_LIBJXL*/ + +/** + * vips_jxlload: + * @filename: file to load + * @out: (out): decompressed image + * @...: %NULL-terminated list of optional named arguments + * + * Read a JPEG-XL image. + * + * The JPEG-XL loader and saver are experimental features and may change + * in future libvips versions. + * + * See also: vips_image_new_from_file(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_jxlload( const char *filename, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "jxlload", ap, filename, out ); + va_end( ap ); + + return( result ); +} + +/** + * vips_jxlload_buffer: + * @buf: (array length=len) (element-type guint8): memory area to load + * @len: (type gsize): size of memory area + * @out: (out): image to write + * @...: %NULL-terminated list of optional named arguments + * + * Exactly as vips_jxlload(), but read from a buffer. + * + * Returns: 0 on success, -1 on error. + */ +int +vips_jxlload_buffer( void *buf, size_t len, VipsImage **out, ... ) +{ + va_list ap; + VipsBlob *blob; + int result; + + /* We don't take a copy of the data or free it. + */ + blob = vips_blob_new( NULL, buf, len ); + + va_start( ap, out ); + result = vips_call_split( "jxlload_buffer", ap, blob, out ); + va_end( ap ); + + vips_area_unref( VIPS_AREA( blob ) ); + + return( result ); +} + +/** + * vips_jxlload_source: + * @source: source to load from + * @out: (out): decompressed image + * @...: %NULL-terminated list of optional named arguments + * + * Exactly as vips_jxlload(), but read from a source. + * + * Returns: 0 on success, -1 on error. + */ +int +vips_jxlload_source( VipsSource *source, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "jxlload_source", ap, source, out ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/foreign/jxlsave.c b/libvips/foreign/jxlsave.c new file mode 100644 index 00000000..56a26541 --- /dev/null +++ b/libvips/foreign/jxlsave.c @@ -0,0 +1,823 @@ +/* save as jpeg-xl + * + * 18/3/20 + * - from heifload.c + */ + +/* + + This file is part of VIPS. + + VIPS is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +/* +#define DEBUG_VERBOSE +#define DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include + +#include +#include + +#ifdef HAVE_LIBJXL + +#include +#include + +#include "pforeign.h" + +/* TODO: + * + * - libjxl currently seems to be missing API to attach a profile + * + * - libjxl encode only works in one shot mode, so there's no way to write in + * chunks + * + * - add metadata support EXIF, XMP, etc. api for this is on the way + * + * - add animation support + * + * - libjxl is currently missing error messages (I think) + * + * - fix scRGB gamma + */ + +#define OUTPUT_BUFFER_SIZE (4096) + +typedef struct _VipsForeignSaveJxl { + VipsForeignSave parent_object; + + /* Where to write (set by subclasses). + */ + VipsTarget *target; + + /* Encoder options. + */ + int tier; + double distance; + int effort; + gboolean lossless; + int Q; + + /* Base image properties. + */ + JxlBasicInfo info; + JxlColorEncoding color_encoding; + JxlPixelFormat format; + + /* Encoder state. + */ + void *runner; + JxlEncoder *encoder; + + /* Write buffer. + */ + uint8_t output_buffer[OUTPUT_BUFFER_SIZE]; + +} VipsForeignSaveJxl; + +typedef VipsForeignSaveClass VipsForeignSaveJxlClass; + +G_DEFINE_ABSTRACT_TYPE( VipsForeignSaveJxl, vips_foreign_save_jxl, + VIPS_TYPE_FOREIGN_SAVE ); + +static void +vips_foreign_save_jxl_dispose( GObject *gobject ) +{ + VipsForeignSaveJxl *jxl = (VipsForeignSaveJxl *) gobject; + + VIPS_FREEF( JxlThreadParallelRunnerDestroy, jxl->runner ); + VIPS_FREEF( JxlEncoderDestroy, jxl->encoder ); + + G_OBJECT_CLASS( vips_foreign_save_jxl_parent_class )-> + dispose( gobject ); +} + +static void +vips_foreign_save_jxl_error( VipsForeignSaveJxl *jxl, const char *details ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( jxl ); + + /* TODO ... jxl has no way to get error messages at the moment. + */ + vips_error( class->nickname, "error %s", details ); +} + +#ifdef DEBUG +static void +vips_foreign_save_jxl_print_info( JxlBasicInfo *info ) +{ + printf( "JxlBasicInfo:\n" ); + printf( " have_container = %d\n", info->have_container ); + printf( " xsize = %d\n", info->xsize ); + printf( " ysize = %d\n", info->ysize ); + printf( " bits_per_sample = %d\n", info->bits_per_sample ); + printf( " exponent_bits_per_sample = %d\n", + info->exponent_bits_per_sample ); + printf( " intensity_target = %g\n", info->intensity_target ); + printf( " min_nits = %g\n", info->min_nits ); + printf( " relative_to_max_display = %d\n", + info->relative_to_max_display ); + printf( " linear_below = %g\n", info->linear_below ); + printf( " uses_original_profile = %d\n", + info->uses_original_profile ); + printf( " have_preview = %d\n", info->have_preview ); + printf( " have_animation = %d\n", info->have_animation ); + printf( " orientation = %d\n", info->orientation ); + printf( " num_color_channels = %d\n", info->num_color_channels ); + printf( " num_extra_channels = %d\n", info->num_extra_channels ); + printf( " alpha_bits = %d\n", info->alpha_bits ); + printf( " alpha_exponent_bits = %d\n", info->alpha_exponent_bits ); + printf( " alpha_premultiplied = %d\n", info->alpha_premultiplied ); + printf( " preview.xsize = %d\n", info->preview.xsize ); + printf( " preview.ysize = %d\n", info->preview.ysize ); + printf( " animation.tps_numerator = %d\n", + info->animation.tps_numerator ); + printf( " animation.tps_denominator = %d\n", + info->animation.tps_denominator ); + printf( " animation.num_loops = %d\n", info->animation.num_loops ); + printf( " animation.have_timecodes = %d\n", + info->animation.have_timecodes ); +} + +static void +vips_foreign_save_jxl_print_format( JxlPixelFormat *format ) +{ + printf( "JxlPixelFormat:\n" ); + printf( " data_type = " ); + switch( format->data_type ) { + case JXL_TYPE_UINT8: + printf( "JXL_TYPE_UINT8" ); + break; + + case JXL_TYPE_UINT16: + printf( "JXL_TYPE_UINT16" ); + break; + + case JXL_TYPE_UINT32: + printf( "JXL_TYPE_UINT32" ); + break; + + case JXL_TYPE_FLOAT: + printf( "JXL_TYPE_FLOAT" ); + break; + + default: + printf( "(unknown)" ); + break; + } + printf( "\n" ); + printf( " num_channels = %d\n", format->num_channels ); + printf( " endianness = %d\n", format->endianness ); + printf( " align = %zd\n", format->align ); +} + +static void +vips_foreign_save_jxl_print_status( JxlEncoderStatus status ) +{ + switch( status ) { + case JXL_ENC_SUCCESS: + printf( "JXL_ENC_SUCCESS\n" ); + break; + + case JXL_ENC_ERROR: + printf( "JXL_ENC_ERROR\n" ); + break; + + case JXL_ENC_NEED_MORE_OUTPUT: + printf( "JXL_ENC_NEED_MORE_OUTPUT\n" ); + break; + + case JXL_ENC_NOT_SUPPORTED: + printf( "JXL_ENC_NOT_SUPPORTED\n" ); + break; + + default: + printf( "JXL_ENC_\n" ); + break; + } +} +#endif /*DEBUG*/ + +static int +vips_foreign_save_jxl_build( VipsObject *object ) +{ + VipsForeignSave *save = (VipsForeignSave *) object; + VipsForeignSaveJxl *jxl = (VipsForeignSaveJxl *) object; + + JxlEncoderOptions *options; + JxlEncoderStatus status; + + if( VIPS_OBJECT_CLASS( vips_foreign_save_jxl_parent_class )-> + build( object ) ) + return( -1 ); + + /* If Q is set and distance is not, use Q to set a rough distance + * value. Formula stolen from cjxl.c and very roughly approximates + * libjpeg values. + */ + if( !vips_object_argument_isset( object, "distance" ) ) + jxl->distance = jxl->Q >= 30 ? + 0.1 + (100 - jxl->Q) * 0.09 : + 6.4 + pow(2.5, (30 - jxl->Q) / 5.0f) / 6.25f; + + /* Distance 0 is lossless. libjxl will fail for lossy distance 0. + */ + if( jxl->distance == 0 ) + jxl->lossless = TRUE; + + jxl->runner = JxlThreadParallelRunnerCreate( NULL, + vips_concurrency_get() ); + jxl->encoder = JxlEncoderCreate( NULL ); + + if( JxlEncoderSetParallelRunner( jxl->encoder, + JxlThreadParallelRunner, jxl->runner ) ) { + vips_foreign_save_jxl_error( jxl, + "JxlDecoderSetParallelRunner" ); + return( -1 ); + } + + switch( save->ready->BandFmt ) { + case VIPS_FORMAT_UCHAR: + jxl->info.bits_per_sample = 8; + jxl->info.exponent_bits_per_sample = 0; + jxl->format.data_type = JXL_TYPE_UINT8; + break; + + case VIPS_FORMAT_USHORT: + jxl->info.bits_per_sample = 16; + jxl->info.exponent_bits_per_sample = 0; + jxl->format.data_type = JXL_TYPE_UINT16; + break; + + case VIPS_FORMAT_UINT: + jxl->info.bits_per_sample = 32; + jxl->info.exponent_bits_per_sample = 0; + jxl->format.data_type = JXL_TYPE_UINT32; + break; + + case VIPS_FORMAT_FLOAT: + jxl->info.bits_per_sample = 32; + jxl->info.exponent_bits_per_sample = 8; + jxl->format.data_type = JXL_TYPE_FLOAT; + break; + + default: + g_assert_not_reached(); + break; + } + + switch( save->ready->Type ) { + case VIPS_INTERPRETATION_B_W: + case VIPS_INTERPRETATION_GREY16: + jxl->info.num_color_channels = 1; + break; + + case VIPS_INTERPRETATION_sRGB: + case VIPS_INTERPRETATION_scRGB: + case VIPS_INTERPRETATION_RGB16: + jxl->info.num_color_channels = 3; + break; + + default: + jxl->info.num_color_channels = save->ready->Bands; + } + jxl->info.num_extra_channels = VIPS_MAX( 0, + save->ready->Bands - jxl->info.num_color_channels ); + + jxl->info.xsize = save->ready->Xsize; + jxl->info.ysize = save->ready->Ysize; + jxl->format.num_channels = save->ready->Bands; + jxl->format.endianness = JXL_NATIVE_ENDIAN; + jxl->format.align = 0; + + if( vips_image_hasalpha( save->ready ) ) { + jxl->info.alpha_bits = jxl->info.bits_per_sample; + jxl->info.alpha_exponent_bits = + jxl->info.exponent_bits_per_sample; + } + else { + jxl->info.alpha_exponent_bits = 0; + jxl->info.alpha_bits = 0; + } + + if( vips_image_get_typeof( save->ready, "stonits" ) ) { + double stonits; + + if( vips_image_get_double( save->ready, "stonits", &stonits ) ) + return( -1 ); + jxl->info.intensity_target = stonits; + } + + /* FIXME libjxl doesn't seem to have this API yet. + * + if( vips_image_get_typeof( save->ready, VIPS_META_ICC_NAME ) ) { + const void *data; + size_t length; + + if( vips_image_get_blob( save->ready, + VIPS_META_ICC_NAME, &data, &length ) ) + return( -1 ); + + jxl->info.uses_original_profile = JXL_TRUE; + ... attach profile + } + else + jxl->info.uses_original_profile = JXL_FALSE; + */ + + /* Remove this when libjxl gets API to attach an ICC profile. + */ + jxl->info.uses_original_profile = JXL_FALSE; + + if( JxlEncoderSetBasicInfo( jxl->encoder, &jxl->info ) ) { + vips_foreign_save_jxl_error( jxl, "JxlEncoderSetBasicInfo" ); + return( -1 ); + } + + JxlColorEncodingSetToSRGB( &jxl->color_encoding, + jxl->format.num_channels < 3 ); + if( JxlEncoderSetColorEncoding( jxl->encoder, &jxl->color_encoding ) ) { + vips_foreign_save_jxl_error( jxl, + "JxlEncoderSetColorEncoding" ); + return( -1 ); + } + + /* Render the entire image in memory. libjxl seems to be missing + * tile-based write at the moment. + */ + if( vips_image_wio_input( save->ready ) ) + return( -1 ); + + options = JxlEncoderOptionsCreate( jxl->encoder, NULL ); + JxlEncoderOptionsSetDecodingSpeed( options, jxl->tier ); + JxlEncoderOptionsSetDistance( options, jxl->distance ); + JxlEncoderOptionsSetEffort( options, jxl->effort ); + JxlEncoderOptionsSetLossless( options, jxl->lossless ); + +#ifdef DEBUG + vips_foreign_save_jxl_print_info( &jxl->info ); + vips_foreign_save_jxl_print_format( &jxl->format ); + printf( "JxlEncoderOptions:\n" ); + printf( " tier = %d\n", jxl->tier ); + printf( " distance = %g\n", jxl->distance ); + printf( " effort = %d\n", jxl->effort ); + printf( " lossless = %d\n", jxl->lossless ); +#endif /*DEBUG*/ + + if( JxlEncoderAddImageFrame( options, &jxl->format, + VIPS_IMAGE_ADDR( save->ready, 0, 0 ), + VIPS_IMAGE_SIZEOF_IMAGE( save->ready ) ) ) { + vips_foreign_save_jxl_error( jxl, "JxlEncoderAddImageFrame" ); + return( -1 ); + } + + do { + uint8_t *out; + size_t avail_out; + + out = jxl->output_buffer; + avail_out = OUTPUT_BUFFER_SIZE; + status = JxlEncoderProcessOutput( jxl->encoder, + &out, &avail_out ); + switch( status ) { + case JXL_ENC_SUCCESS: + case JXL_ENC_NEED_MORE_OUTPUT: + if( vips_target_write( jxl->target, + jxl->output_buffer, + OUTPUT_BUFFER_SIZE - avail_out ) ) + return( -1 ); + break; + + default: + vips_foreign_save_jxl_error( jxl, + "JxlEncoderProcessOutput" ); +#ifdef DEBUG + vips_foreign_save_jxl_print_status( status ); +#endif /*DEBUG*/ + return( -1 ); + } + } while( status != JXL_ENC_SUCCESS ); + + vips_target_finish( jxl->target ); + + return( 0 ); +} + +/* Save a bit of typing. + */ +#define UC VIPS_FORMAT_UCHAR +#define US VIPS_FORMAT_USHORT +#define UI VIPS_FORMAT_UINT +#define F VIPS_FORMAT_FLOAT + +/* Type promotion for save ... unsigned ints + float + double. + */ +static int bandfmt_jpeg[10] = { + /* UC C US S UI I F X D DX */ + UC, UC, US, US, UI, UI, F, F, F, F +}; + +static void +vips_foreign_save_jxl_class_init( VipsForeignSaveJxlClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignClass *foreign_class = (VipsForeignClass *) class; + VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class; + + gobject_class->dispose = vips_foreign_save_jxl_dispose; + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "jxlsave_base"; + object_class->description = _( "save image in JPEG-XL format" ); + object_class->build = vips_foreign_save_jxl_build; + + foreign_class->suffs = vips__jxl_suffs; + + save_class->saveable = VIPS_SAVEABLE_ANY; + save_class->format_table = bandfmt_jpeg; + + VIPS_ARG_INT( class, "tier", 10, + _( "Tier" ), + _( "Decode speed tier" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveJxl, tier ), + 0, 4, 0 ); + + VIPS_ARG_DOUBLE( class, "distance", 11, + _( "Distance" ), + _( "Target butteraugli distance" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveJxl, distance ), + 0, 15, 1.0 ); + + VIPS_ARG_INT( class, "effort", 12, + _( "effort" ), + _( "Encoding effort" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveJxl, effort ), + 3, 9, 7 ); + + VIPS_ARG_BOOL( class, "lossless", 13, + _( "Lossless" ), + _( "Enable lossless compression" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveJxl, lossless ), + FALSE ); + + VIPS_ARG_INT( class, "Q", 14, + _( "Q" ), + _( "Quality factor" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveJxl, Q ), + 0, 100, 75 ); + +} + +static void +vips_foreign_save_jxl_init( VipsForeignSaveJxl *jxl ) +{ + jxl->tier = 0; + jxl->distance = 1.0; + jxl->effort = 7; + jxl->lossless = FALSE; + jxl->Q = 75; +} + +typedef struct _VipsForeignSaveJxlFile { + VipsForeignSaveJxl parent_object; + + /* Filename for save. + */ + char *filename; + +} VipsForeignSaveJxlFile; + +typedef VipsForeignSaveJxlClass VipsForeignSaveJxlFileClass; + +G_DEFINE_TYPE( VipsForeignSaveJxlFile, vips_foreign_save_jxl_file, + vips_foreign_save_jxl_get_type() ); + +static int +vips_foreign_save_jxl_file_build( VipsObject *object ) +{ + VipsForeignSaveJxl *jxl = (VipsForeignSaveJxl *) object; + VipsForeignSaveJxlFile *file = (VipsForeignSaveJxlFile *) object; + + if( !(jxl->target = vips_target_new_to_file( file->filename )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_save_jxl_file_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_save_jxl_file_class_init( VipsForeignSaveJxlFileClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "jxlsave"; + object_class->build = vips_foreign_save_jxl_file_build; + + VIPS_ARG_STRING( class, "filename", 1, + _( "Filename" ), + _( "Filename to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveJxlFile, filename ), + NULL ); + +} + +static void +vips_foreign_save_jxl_file_init( VipsForeignSaveJxlFile *file ) +{ +} + +typedef struct _VipsForeignSaveJxlBuffer { + VipsForeignSaveJxl parent_object; + + /* Save to a buffer. + */ + VipsArea *buf; + +} VipsForeignSaveJxlBuffer; + +typedef VipsForeignSaveJxlClass VipsForeignSaveJxlBufferClass; + +G_DEFINE_TYPE( VipsForeignSaveJxlBuffer, vips_foreign_save_jxl_buffer, + vips_foreign_save_jxl_get_type() ); + +static int +vips_foreign_save_jxl_buffer_build( VipsObject *object ) +{ + VipsForeignSaveJxl *jxl = (VipsForeignSaveJxl *) object; + VipsForeignSaveJxlBuffer *buffer = + (VipsForeignSaveJxlBuffer *) object; + + VipsBlob *blob; + + if( !(jxl->target = vips_target_new_to_memory()) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_save_jxl_buffer_parent_class )-> + build( object ) ) + return( -1 ); + + g_object_get( jxl->target, "blob", &blob, NULL ); + g_object_set( buffer, "buffer", blob, NULL ); + vips_area_unref( VIPS_AREA( blob ) ); + + return( 0 ); +} + +static void +vips_foreign_save_jxl_buffer_class_init( + VipsForeignSaveJxlBufferClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "jxlsave_buffer"; + object_class->build = vips_foreign_save_jxl_buffer_build; + + VIPS_ARG_BOXED( class, "buffer", 1, + _( "Buffer" ), + _( "Buffer to save to" ), + VIPS_ARGUMENT_REQUIRED_OUTPUT, + G_STRUCT_OFFSET( VipsForeignSaveJxlBuffer, buf ), + VIPS_TYPE_BLOB ); + +} + +static void +vips_foreign_save_jxl_buffer_init( VipsForeignSaveJxlBuffer *buffer ) +{ +} + +typedef struct _VipsForeignSaveJxlTarget { + VipsForeignSaveJxl parent_object; + + VipsTarget *target; +} VipsForeignSaveJxlTarget; + +typedef VipsForeignSaveJxlClass VipsForeignSaveJxlTargetClass; + +G_DEFINE_TYPE( VipsForeignSaveJxlTarget, vips_foreign_save_jxl_target, + vips_foreign_save_jxl_get_type() ); + +static int +vips_foreign_save_jxl_target_build( VipsObject *object ) +{ + VipsForeignSaveJxl *jxl = (VipsForeignSaveJxl *) object; + VipsForeignSaveJxlTarget *target = + (VipsForeignSaveJxlTarget *) object; + + if( target->target ) { + jxl->target = target->target; + g_object_ref( jxl->target ); + } + + if( VIPS_OBJECT_CLASS( vips_foreign_save_jxl_target_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_save_jxl_target_class_init( + VipsForeignSaveJxlTargetClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "jxlsave_target"; + object_class->build = vips_foreign_save_jxl_target_build; + + VIPS_ARG_OBJECT( class, "target", 1, + _( "Target" ), + _( "Target to save to" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveJxlTarget, target ), + VIPS_TYPE_TARGET ); + +} + +static void +vips_foreign_save_jxl_target_init( VipsForeignSaveJxlTarget *target ) +{ +} + +#endif /*HAVE_LIBOPENJXL*/ + +/** + * vips_jxlsave: (method) + * @in: image to save + * @filename: file to write to + * @...: %NULL-terminated list of optional named arguments + * + * Optional arguments: + * + * * @tier: %gint, decode speed tier + * * @distance: %gdouble, maximum encoding error + * * @effort: %gint, encoding effort + * * @lossless: %gboolean, enables lossless compression + * * @Q: %gint, quality setting + * + * Write a VIPS image to a file in JPEG-XL format. + * + * The JPEG-XL loader and saver are experimental features and may change + * in future libvips versions. + * + * @tier sets the overall decode speed the encoder will target. Minimum is 0 + * (highest quality), and maximum is 4 (lowest quality). Default is 0. + * + * @distance sets the target maximum encoding error. Minimum is 0 + * (highest quality), and maximum is 15 (lowest quality). Default is 1.0 + * (visually lossless). + * + * As a convenience, you can also use @Q to set @distance. @Q uses + * approximately the same scale as regular JPEG. + * + * Set @lossless to enable lossless compresion. + * + * Returns: 0 on success, -1 on error. + */ +int +vips_jxlsave( VipsImage *in, const char *filename, ... ) +{ + va_list ap; + int result; + + va_start( ap, filename ); + result = vips_call_split( "jxlsave", ap, in, filename ); + va_end( ap ); + + return( result ); +} + +/** + * vips_jxlsave_buffer: (method) + * @in: image to save + * @buf: (array length=len) (element-type guint8): return output buffer here + * @len: (type gsize): return output length here + * @...: %NULL-terminated list of optional named arguments + * + * Optional arguments: + * + * * @tier: %gint, decode speed tier + * * @distance: %gdouble, maximum encoding error + * * @effort: %gint, encoding effort + * * @lossless: %gboolean, enables lossless compression + * * @Q: %gint, quality setting + * + * As vips_jxlsave(), but save to a memory buffer. + * + * See also: vips_jxlsave(), vips_image_write_to_target(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_jxlsave_buffer( VipsImage *in, void **buf, size_t *len, ... ) +{ + va_list ap; + VipsArea *area; + int result; + + area = NULL; + + va_start( ap, len ); + result = vips_call_split( "jxlsave_buffer", ap, in, &area ); + va_end( ap ); + + if( !result && + area ) { + if( buf ) { + *buf = area->data; + area->free_fn = NULL; + } + if( len ) + *len = area->length; + + vips_area_unref( area ); + } + + return( result ); +} + +/** + * vips_jxlsave_target: (method) + * @in: image to save + * @target: save image to this target + * @...: %NULL-terminated list of optional named arguments + * + * Optional arguments: + * + * * @tier: %gint, decode speed tier + * * @distance: %gdouble, maximum encoding error + * * @effort: %gint, encoding effort + * * @lossless: %gboolean, enables lossless compression + * * @Q: %gint, quality setting + * + * As vips_jxlsave(), but save to a target. + * + * See also: vips_jxlsave(), vips_image_write_to_target(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_jxlsave_target( VipsImage *in, VipsTarget *target, ... ) +{ + va_list ap; + int result; + + va_start( ap, target ); + result = vips_call_split( "jxlsave_target", ap, in, target ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/foreign/libnsgif/libnsgif.c b/libvips/foreign/libnsgif/libnsgif.c index 429bcd1a..1fee573d 100644 --- a/libvips/foreign/libnsgif/libnsgif.c +++ b/libvips/foreign/libnsgif/libnsgif.c @@ -79,21 +79,21 @@ */ static gif_result gif_initialise_sprite(gif_animation *gif, - unsigned int width, - unsigned int height) + unsigned int width, + unsigned int height) { - /* Already allocated? */ - if (gif->frame_image) { - return GIF_OK; - } + /* Already allocated? */ + if (gif->frame_image) { + return GIF_OK; + } - assert(gif->bitmap_callbacks.bitmap_create); - gif->frame_image = gif->bitmap_callbacks.bitmap_create(width, height); - if (gif->frame_image == NULL) { - return GIF_INSUFFICIENT_MEMORY; - } + assert(gif->bitmap_callbacks.bitmap_create); + gif->frame_image = gif->bitmap_callbacks.bitmap_create(width, height); + if (gif->frame_image == NULL) { + return GIF_INSUFFICIENT_MEMORY; + } - return GIF_OK; + return GIF_OK; } @@ -108,117 +108,117 @@ gif_initialise_sprite(gif_animation *gif, static gif_result gif_initialise_frame_extensions(gif_animation *gif, const int frame) { - unsigned char *gif_data, *gif_end; - int gif_bytes; - unsigned int block_size; + unsigned char *gif_data, *gif_end; + int gif_bytes; + unsigned int block_size; - /* Get our buffer position etc. */ - gif_data = (unsigned char *)(gif->gif_data + gif->buffer_position); - gif_end = (unsigned char *)(gif->gif_data + gif->buffer_size); + /* Get our buffer position etc. */ + gif_data = (unsigned char *)(gif->gif_data + gif->buffer_position); + gif_end = (unsigned char *)(gif->gif_data + gif->buffer_size); - /* Initialise the extensions */ - while (gif_data < gif_end && gif_data[0] == GIF_EXTENSION_INTRODUCER) { - ++gif_data; - if ((gif_bytes = (gif_end - gif_data)) < 1) { - return GIF_INSUFFICIENT_FRAME_DATA; - } + /* Initialise the extensions */ + while (gif_data < gif_end && gif_data[0] == GIF_EXTENSION_INTRODUCER) { + ++gif_data; + if ((gif_bytes = (gif_end - gif_data)) < 1) { + return GIF_INSUFFICIENT_FRAME_DATA; + } - /* Switch on extension label */ - switch (gif_data[0]) { - case GIF_EXTENSION_GRAPHIC_CONTROL: - /* 6-byte Graphic Control Extension is: - * - * +0 CHAR Graphic Control Label - * +1 CHAR Block Size - * +2 CHAR __Packed Fields__ - * 3BITS Reserved - * 3BITS Disposal Method - * 1BIT User Input Flag - * 1BIT Transparent Color Flag - * +3 SHORT Delay Time - * +5 CHAR Transparent Color Index - */ - if (gif_bytes < 6) { - return GIF_INSUFFICIENT_FRAME_DATA; - } + /* Switch on extension label */ + switch (gif_data[0]) { + case GIF_EXTENSION_GRAPHIC_CONTROL: + /* 6-byte Graphic Control Extension is: + * + * +0 CHAR Graphic Control Label + * +1 CHAR Block Size + * +2 CHAR __Packed Fields__ + * 3BITS Reserved + * 3BITS Disposal Method + * 1BIT User Input Flag + * 1BIT Transparent Color Flag + * +3 SHORT Delay Time + * +5 CHAR Transparent Color Index + */ + if (gif_bytes < 6) { + return GIF_INSUFFICIENT_FRAME_DATA; + } - gif->frames[frame].frame_delay = gif_data[3] | (gif_data[4] << 8); - if (gif_data[2] & GIF_TRANSPARENCY_MASK) { - gif->frames[frame].transparency = true; - gif->frames[frame].transparency_index = gif_data[5]; - } - gif->frames[frame].disposal_method = ((gif_data[2] & GIF_DISPOSAL_MASK) >> 2); - /* I have encountered documentation and GIFs in the - * wild that use 0x04 to restore the previous frame, - * rather than the officially documented 0x03. I - * believe some (older?) software may even actually - * export this way. We handle this as a type of - * "quirks" mode. - */ - if (gif->frames[frame].disposal_method == GIF_FRAME_QUIRKS_RESTORE) { - gif->frames[frame].disposal_method = GIF_FRAME_RESTORE; - } - gif_data += (2 + gif_data[1]); - break; + gif->frames[frame].frame_delay = gif_data[3] | (gif_data[4] << 8); + if (gif_data[2] & GIF_TRANSPARENCY_MASK) { + gif->frames[frame].transparency = true; + gif->frames[frame].transparency_index = gif_data[5]; + } + gif->frames[frame].disposal_method = ((gif_data[2] & GIF_DISPOSAL_MASK) >> 2); + /* I have encountered documentation and GIFs in the + * wild that use 0x04 to restore the previous frame, + * rather than the officially documented 0x03. I + * believe some (older?) software may even actually + * export this way. We handle this as a type of + * "quirks" mode. + */ + if (gif->frames[frame].disposal_method == GIF_FRAME_QUIRKS_RESTORE) { + gif->frames[frame].disposal_method = GIF_FRAME_RESTORE; + } + gif_data += (2 + gif_data[1]); + break; - case GIF_EXTENSION_APPLICATION: - /* 14-byte+ Application Extension is: - * - * +0 CHAR Application Extension Label - * +1 CHAR Block Size - * +2 8CHARS Application Identifier - * +10 3CHARS Appl. Authentication Code - * +13 1-256 Application Data (Data sub-blocks) - */ - if (gif_bytes < 17) { - return GIF_INSUFFICIENT_FRAME_DATA; - } - if ((gif_data[1] == 0x0b) && - (strncmp((const char *) gif_data + 2, - "NETSCAPE2.0", 11) == 0) && - (gif_data[13] == 0x03) && - (gif_data[14] == 0x01)) { - gif->loop_count = gif_data[15] | (gif_data[16] << 8); - } - gif_data += (2 + gif_data[1]); - break; + case GIF_EXTENSION_APPLICATION: + /* 14-byte+ Application Extension is: + * + * +0 CHAR Application Extension Label + * +1 CHAR Block Size + * +2 8CHARS Application Identifier + * +10 3CHARS Appl. Authentication Code + * +13 1-256 Application Data (Data sub-blocks) + */ + if (gif_bytes < 17) { + return GIF_INSUFFICIENT_FRAME_DATA; + } + if ((gif_data[1] == 0x0b) && + (strncmp((const char *) gif_data + 2, + "NETSCAPE2.0", 11) == 0) && + (gif_data[13] == 0x03) && + (gif_data[14] == 0x01)) { + gif->loop_count = gif_data[15] | (gif_data[16] << 8); + } + gif_data += (2 + gif_data[1]); + break; - case GIF_EXTENSION_COMMENT: - /* Move the pointer to the first data sub-block Skip 1 - * byte for the extension label - */ - ++gif_data; - break; + case GIF_EXTENSION_COMMENT: + /* Move the pointer to the first data sub-block Skip 1 + * byte for the extension label + */ + ++gif_data; + break; - default: - /* Move the pointer to the first data sub-block Skip 2 - * bytes for the extension label and size fields Skip - * the extension size itself - */ - if (gif_bytes < 2) { - return GIF_INSUFFICIENT_FRAME_DATA; - } - gif_data += (2 + gif_data[1]); - } + default: + /* Move the pointer to the first data sub-block Skip 2 + * bytes for the extension label and size fields Skip + * the extension size itself + */ + if (gif_bytes < 2) { + return GIF_INSUFFICIENT_FRAME_DATA; + } + gif_data += (2 + gif_data[1]); + } - /* Repeatedly skip blocks until we get a zero block or run out - * of data This data is ignored by this gif decoder - */ - gif_bytes = (gif_end - gif_data); - block_size = 0; - while (gif_data < gif_end && gif_data[0] != GIF_BLOCK_TERMINATOR) { - block_size = gif_data[0] + 1; - if ((gif_bytes -= block_size) < 0) { - return GIF_INSUFFICIENT_FRAME_DATA; - } - gif_data += block_size; - } - ++gif_data; - } + /* Repeatedly skip blocks until we get a zero block or run out + * of data This data is ignored by this gif decoder + */ + gif_bytes = (gif_end - gif_data); + block_size = 0; + while (gif_data < gif_end && gif_data[0] != GIF_BLOCK_TERMINATOR) { + block_size = gif_data[0] + 1; + if ((gif_bytes -= block_size) < 0) { + return GIF_INSUFFICIENT_FRAME_DATA; + } + gif_data += block_size; + } + ++gif_data; + } - /* Set buffer position and return */ - gif->buffer_position = (gif_data - gif->gif_data); - return GIF_OK; + /* Set buffer position and return */ + gif->buffer_position = (gif_data - gif->gif_data); + return GIF_OK; } @@ -237,192 +237,193 @@ gif_initialise_frame_extensions(gif_animation *gif, const int frame) */ static gif_result gif_initialise_frame(gif_animation *gif) { - int frame; - gif_frame *temp_buf; + int frame; + gif_frame *temp_buf; - unsigned char *gif_data, *gif_end; - int gif_bytes; - unsigned int flags = 0; - unsigned int width, height, offset_x, offset_y; - unsigned int block_size, colour_table_size; - bool first_image = true; - gif_result return_value; + unsigned char *gif_data, *gif_end; + int gif_bytes; + unsigned int flags = 0; + unsigned int width, height, offset_x, offset_y; + unsigned int block_size, colour_table_size; + bool first_image = true; + gif_result return_value; - /* Get the frame to decode and our data position */ - frame = gif->frame_count; + /* Get the frame to decode and our data position */ + frame = gif->frame_count; - /* Get our buffer position etc. */ - gif_data = (unsigned char *)(gif->gif_data + gif->buffer_position); - gif_end = (unsigned char *)(gif->gif_data + gif->buffer_size); - gif_bytes = (gif_end - gif_data); + /* Get our buffer position etc. */ + gif_data = (unsigned char *)(gif->gif_data + gif->buffer_position); + gif_end = (unsigned char *)(gif->gif_data + gif->buffer_size); + gif_bytes = (gif_end - gif_data); - /* Check if we've finished */ - if ((gif_bytes > 0) && (gif_data[0] == GIF_TRAILER)) { - return GIF_OK; - } + /* Check if we've finished */ + if ((gif_bytes > 0) && (gif_data[0] == GIF_TRAILER)) { + return GIF_OK; + } - /* Check if there is enough data remaining. The shortest block of data - * is a 4-byte comment extension + 1-byte block terminator + 1-byte gif - * trailer - */ - if (gif_bytes < 6) { - return GIF_INSUFFICIENT_DATA; - } + /* Check if there is enough data remaining. The shortest block of data + * is a 4-byte comment extension + 1-byte block terminator + 1-byte gif + * trailer + */ + if (gif_bytes < 6) { + return GIF_INSUFFICIENT_DATA; + } - /* We could theoretically get some junk data that gives us millions of - * frames, so we ensure that we don't have a silly number - */ - if (frame > 4096) { - return GIF_FRAME_DATA_ERROR; - } + /* We could theoretically get some junk data that gives us millions of + * frames, so we ensure that we don't have a silly number + */ + if (frame > 4096) { + return GIF_FRAME_DATA_ERROR; + } - /* Get some memory to store our pointers in etc. */ - if ((int)gif->frame_holders <= frame) { - /* Allocate more memory */ - temp_buf = (gif_frame *)realloc(gif->frames, (frame + 1) * sizeof(gif_frame)); - if (temp_buf == NULL) { - return GIF_INSUFFICIENT_MEMORY; - } - gif->frames = temp_buf; - gif->frame_holders = frame + 1; - } + /* Get some memory to store our pointers in etc. */ + if ((int)gif->frame_holders <= frame) { + /* Allocate more memory */ + temp_buf = (gif_frame *)realloc(gif->frames, (frame + 1) * sizeof(gif_frame)); + if (temp_buf == NULL) { + return GIF_INSUFFICIENT_MEMORY; + } + gif->frames = temp_buf; + gif->frame_holders = frame + 1; + } - /* Store our frame pointer. We would do it when allocating except we - * start off with one frame allocated so we can always use realloc. - */ - gif->frames[frame].frame_pointer = gif->buffer_position; - gif->frames[frame].display = false; - gif->frames[frame].virgin = true; - gif->frames[frame].disposal_method = 0; - gif->frames[frame].transparency = false; - gif->frames[frame].frame_delay = 100; - gif->frames[frame].redraw_required = false; + /* Store our frame pointer. We would do it when allocating except we + * start off with one frame allocated so we can always use realloc. + */ + gif->frames[frame].frame_pointer = gif->buffer_position; + gif->frames[frame].display = false; + gif->frames[frame].virgin = true; + gif->frames[frame].disposal_method = 0; + gif->frames[frame].transparency = false; + gif->frames[frame].frame_delay = 100; + gif->frames[frame].redraw_required = false; - /* Invalidate any previous decoding we have of this frame */ - if (gif->decoded_frame == frame) { - gif->decoded_frame = GIF_INVALID_FRAME; - } + /* Invalidate any previous decoding we have of this frame */ + if (gif->decoded_frame == frame) { + gif->decoded_frame = GIF_INVALID_FRAME; + } - /* We pretend to initialise the frames, but really we just skip over - * all the data contained within. This is all basically a cut down - * version of gif_decode_frame that doesn't have any of the LZW bits in - * it. - */ + /* We pretend to initialise the frames, but really we just skip over + * all the data contained within. This is all basically a cut down + * version of gif_decode_frame that doesn't have any of the LZW bits in + * it. + */ - /* Initialise any extensions */ - gif->buffer_position = gif_data - gif->gif_data; - return_value = gif_initialise_frame_extensions(gif, frame); - if (return_value != GIF_OK) { - return return_value; - } - gif_data = (gif->gif_data + gif->buffer_position); - gif_bytes = (gif_end - gif_data); + /* Initialise any extensions */ + gif->buffer_position = gif_data - gif->gif_data; + return_value = gif_initialise_frame_extensions(gif, frame); + if (return_value != GIF_OK) { + return return_value; + } + gif_data = (gif->gif_data + gif->buffer_position); + gif_bytes = (gif_end - gif_data); - /* Check if we've finished */ - if ((gif_bytes = (gif_end - gif_data)) < 1) { - return GIF_INSUFFICIENT_FRAME_DATA; - } + /* Check if we've finished */ + if ((gif_bytes = (gif_end - gif_data)) < 1) { + return GIF_INSUFFICIENT_FRAME_DATA; + } - if (gif_data[0] == GIF_TRAILER) { - gif->buffer_position = (gif_data - gif->gif_data); - gif->frame_count = frame + 1; - return GIF_OK; - } + if (gif_data[0] == GIF_TRAILER) { + gif->buffer_position = (gif_data - gif->gif_data); + gif->frame_count = frame + 1; + return GIF_OK; + } - /* If we're not done, there should be an image descriptor */ - if (gif_data[0] != GIF_IMAGE_SEPARATOR) { - return GIF_FRAME_DATA_ERROR; - } + /* If we're not done, there should be an image descriptor */ + if (gif_data[0] != GIF_IMAGE_SEPARATOR) { + return GIF_FRAME_DATA_ERROR; + } - /* Do some simple boundary checking */ - if (gif_bytes < 10) { - return GIF_INSUFFICIENT_FRAME_DATA; - } - offset_x = gif_data[1] | (gif_data[2] << 8); - offset_y = gif_data[3] | (gif_data[4] << 8); - width = gif_data[5] | (gif_data[6] << 8); - height = gif_data[7] | (gif_data[8] << 8); + /* Do some simple boundary checking */ + if (gif_bytes < 10) { + return GIF_INSUFFICIENT_FRAME_DATA; + } + offset_x = gif_data[1] | (gif_data[2] << 8); + offset_y = gif_data[3] | (gif_data[4] << 8); + width = gif_data[5] | (gif_data[6] << 8); + height = gif_data[7] | (gif_data[8] << 8); - /* Set up the redraw characteristics. We have to check for extending - * the area due to multi-image frames. - */ - if (!first_image) { - if (gif->frames[frame].redraw_x > offset_x) { - gif->frames[frame].redraw_width += (gif->frames[frame].redraw_x - offset_x); - gif->frames[frame].redraw_x = offset_x; - } + /* Set up the redraw characteristics. We have to check for extending + * the area due to multi-image frames. + */ + if (!first_image) { + if (gif->frames[frame].redraw_x > offset_x) { + gif->frames[frame].redraw_width += (gif->frames[frame].redraw_x - offset_x); + gif->frames[frame].redraw_x = offset_x; + } - if (gif->frames[frame].redraw_y > offset_y) { - gif->frames[frame].redraw_height += (gif->frames[frame].redraw_y - offset_y); - gif->frames[frame].redraw_y = offset_y; - } + if (gif->frames[frame].redraw_y > offset_y) { + gif->frames[frame].redraw_height += (gif->frames[frame].redraw_y - offset_y); + gif->frames[frame].redraw_y = offset_y; + } - if ((offset_x + width) > (gif->frames[frame].redraw_x + gif->frames[frame].redraw_width)) { - gif->frames[frame].redraw_width = (offset_x + width) - gif->frames[frame].redraw_x; - } + if ((offset_x + width) > (gif->frames[frame].redraw_x + gif->frames[frame].redraw_width)) { + gif->frames[frame].redraw_width = (offset_x + width) - gif->frames[frame].redraw_x; + } - if ((offset_y + height) > (gif->frames[frame].redraw_y + gif->frames[frame].redraw_height)) { - gif->frames[frame].redraw_height = (offset_y + height) - gif->frames[frame].redraw_y; - } - } else { - first_image = false; - gif->frames[frame].redraw_x = offset_x; - gif->frames[frame].redraw_y = offset_y; - gif->frames[frame].redraw_width = width; - gif->frames[frame].redraw_height = height; - } + if ((offset_y + height) > (gif->frames[frame].redraw_y + gif->frames[frame].redraw_height)) { + gif->frames[frame].redraw_height = (offset_y + height) - gif->frames[frame].redraw_y; + } + } else { + first_image = false; + gif->frames[frame].redraw_x = offset_x; + gif->frames[frame].redraw_y = offset_y; + gif->frames[frame].redraw_width = width; + gif->frames[frame].redraw_height = height; + } - /* if we are clearing the background then we need to redraw enough to - * cover the previous frame too - */ - gif->frames[frame].redraw_required = ((gif->frames[frame].disposal_method == GIF_FRAME_CLEAR) || - (gif->frames[frame].disposal_method == GIF_FRAME_RESTORE)); + /* if we are clearing the background then we need to redraw enough to + * cover the previous frame too + */ + 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]; - colour_table_size = 2 << (flags & GIF_COLOUR_TABLE_SIZE_MASK); + /* Decode the flags */ + flags = gif_data[9]; + colour_table_size = 2 << (flags & GIF_COLOUR_TABLE_SIZE_MASK); - /* Move our data onwards and remember we've got a bit of this frame */ - gif_data += 10; - gif_bytes = (gif_end - gif_data); - gif->frame_count_partial = frame + 1; + /* Move our data onwards and remember we've got a bit of this frame */ + gif_data += 10; + gif_bytes = (gif_end - gif_data); + gif->frame_count_partial = frame + 1; - /* Skip the local colour table */ - if (flags & GIF_COLOUR_TABLE_MASK) { - gif_data += 3 * colour_table_size; - if ((gif_bytes = (gif_end - gif_data)) < 0) { - return GIF_INSUFFICIENT_FRAME_DATA; - } - } + /* Skip the local colour table */ + if (flags & GIF_COLOUR_TABLE_MASK) { + gif_data += 3 * colour_table_size; + if ((gif_bytes = (gif_end - gif_data)) < 0) { + return GIF_INSUFFICIENT_FRAME_DATA; + } + } - /* Ensure we have a correct code size */ - if (gif_bytes < 1) { - return GIF_INSUFFICIENT_FRAME_DATA; - } - if (gif_data[0] >= LZW_CODE_MAX) { - return GIF_DATA_ERROR; - } + /* Ensure we have a correct code size */ + if (gif_bytes < 1) { + return GIF_INSUFFICIENT_FRAME_DATA; + } + if (gif_data[0] >= LZW_CODE_MAX) { + return GIF_DATA_ERROR; + } - /* Move our pointer to the actual image data */ - gif_data++; - --gif_bytes; + /* Move our pointer to the actual image data */ + gif_data++; + --gif_bytes; - /* Repeatedly skip blocks until we get a zero block or run out of data - * These blocks of image data are processed later by gif_decode_frame() - */ - block_size = 0; - while (block_size != 1) { - if (gif_bytes < 1) return GIF_INSUFFICIENT_FRAME_DATA; - block_size = gif_data[0] + 1; - /* Check if the frame data runs off the end of the file */ - if ((int)(gif_bytes - block_size) < 0) { + /* Repeatedly skip blocks until we get a zero block or run out of data + * These blocks of image data are processed later by gif_decode_frame() + */ + block_size = 0; + while (block_size != 1) { + if (gif_bytes < 1) return GIF_INSUFFICIENT_FRAME_DATA; + block_size = gif_data[0] + 1; + /* Check if the frame data runs off the end of the file */ + if ((int)(gif_bytes - block_size) < 0) { /* jcupitt 15/9/19 * * There was code here to set a TRAILER tag. But this @@ -432,31 +433,29 @@ static gif_result gif_initialise_frame(gif_animation *gif) * Instead, just signal insufficient frame data. */ return GIF_INSUFFICIENT_FRAME_DATA; - } else { - gif_bytes -= block_size; - gif_data += block_size; - } - } + } else { + gif_bytes -= block_size; + gif_data += block_size; + } + } - /* Add the frame and set the display flag */ - gif->buffer_position = gif_data - gif->gif_data; - gif->frame_count = frame + 1; - gif->frames[frame].display = true; + /* Add the frame and set the display flag */ + gif->buffer_position = gif_data - gif->gif_data; + gif->frame_count = frame + 1; + gif->frames[frame].display = true; - /* Check if we've finished */ - if (gif_bytes < 1) { - return GIF_INSUFFICIENT_FRAME_DATA; - } else { - if (gif_data[0] == GIF_TRAILER) { - return GIF_OK; - } - } - return GIF_WORKING; + /* Check if we've finished */ + if (gif_bytes < 1) { + return GIF_INSUFFICIENT_FRAME_DATA; + } else { + if (gif_data[0] == GIF_TRAILER) { + return GIF_OK; + } + } + return GIF_WORKING; } - - /** * Skips the frame's extensions (which have been previously initialised) * @@ -466,313 +465,313 @@ static gif_result gif_initialise_frame(gif_animation *gif) */ static gif_result gif_skip_frame_extensions(gif_animation *gif) { - unsigned char *gif_data, *gif_end; - int gif_bytes; - unsigned int block_size; + unsigned char *gif_data, *gif_end; + int gif_bytes; + unsigned int block_size; - /* Get our buffer position etc. */ - gif_data = (unsigned char *)(gif->gif_data + gif->buffer_position); - gif_end = (unsigned char *)(gif->gif_data + gif->buffer_size); - gif_bytes = (gif_end - gif_data); + /* Get our buffer position etc. */ + gif_data = (unsigned char *)(gif->gif_data + gif->buffer_position); + gif_end = (unsigned char *)(gif->gif_data + gif->buffer_size); + gif_bytes = (gif_end - gif_data); - /* Skip the extensions */ - while (gif_data < gif_end && gif_data[0] == GIF_EXTENSION_INTRODUCER) { - ++gif_data; - if (gif_data >= gif_end) { - return GIF_INSUFFICIENT_FRAME_DATA; - } + /* Skip the extensions */ + while (gif_data < gif_end && gif_data[0] == GIF_EXTENSION_INTRODUCER) { + ++gif_data; + if (gif_data >= gif_end) { + return GIF_INSUFFICIENT_FRAME_DATA; + } - /* Switch on extension label */ - switch(gif_data[0]) { - case GIF_EXTENSION_COMMENT: - /* Move the pointer to the first data sub-block - * 1 byte for the extension label - */ - ++gif_data; - break; + /* Switch on extension label */ + switch(gif_data[0]) { + case GIF_EXTENSION_COMMENT: + /* Move the pointer to the first data sub-block + * 1 byte for the extension label + */ + ++gif_data; + break; - default: - /* Move the pointer to the first data sub-block 2 bytes - * for the extension label and size fields Skip the - * extension size itself - */ - if (gif_data + 1 >= gif_end) { - return GIF_INSUFFICIENT_FRAME_DATA; - } - gif_data += (2 + gif_data[1]); - } + default: + /* Move the pointer to the first data sub-block 2 bytes + * for the extension label and size fields Skip the + * extension size itself + */ + if (gif_data + 1 >= gif_end) { + return GIF_INSUFFICIENT_FRAME_DATA; + } + gif_data += (2 + gif_data[1]); + } - /* Repeatedly skip blocks until we get a zero block or run out - * of data This data is ignored by this gif decoder - */ - gif_bytes = (gif_end - gif_data); - block_size = 0; - while (gif_data < gif_end && gif_data[0] != GIF_BLOCK_TERMINATOR) { - block_size = gif_data[0] + 1; - if ((gif_bytes -= block_size) < 0) { - return GIF_INSUFFICIENT_FRAME_DATA; - } - gif_data += block_size; - } - ++gif_data; - } + /* Repeatedly skip blocks until we get a zero block or run out + * of data This data is ignored by this gif decoder + */ + gif_bytes = (gif_end - gif_data); + block_size = 0; + while (gif_data < gif_end && gif_data[0] != GIF_BLOCK_TERMINATOR) { + block_size = gif_data[0] + 1; + if ((gif_bytes -= block_size) < 0) { + return GIF_INSUFFICIENT_FRAME_DATA; + } + gif_data += block_size; + } + ++gif_data; + } - /* Set buffer position and return */ - gif->buffer_position = (gif_data - gif->gif_data); - return GIF_OK; + /* Set buffer position and return */ + gif->buffer_position = (gif_data - gif->gif_data); + return GIF_OK; } static unsigned int gif_interlaced_line(int height, int y) { - if ((y << 3) < height) { - return (y << 3); - } - y -= ((height + 7) >> 3); - if ((y << 3) < (height - 4)) { - return (y << 3) + 4; - } - y -= ((height + 3) >> 3); - if ((y << 2) < (height - 2)) { - return (y << 2) + 2; - } - y -= ((height + 1) >> 2); - return (y << 1) + 1; + if ((y << 3) < height) { + return (y << 3); + } + y -= ((height + 7) >> 3); + if ((y << 3) < (height - 4)) { + return (y << 3) + 4; + } + y -= ((height + 3) >> 3); + if ((y << 2) < (height - 2)) { + return (y << 2) + 2; + } + y -= ((height + 1) >> 2); + return (y << 1) + 1; } static gif_result gif_error_from_lzw(lzw_result l_res) { - static const gif_result g_res[] = { - [LZW_OK] = GIF_OK, - [LZW_OK_EOD] = GIF_END_OF_FRAME, - [LZW_NO_MEM] = GIF_INSUFFICIENT_MEMORY, - [LZW_NO_DATA] = GIF_INSUFFICIENT_FRAME_DATA, - [LZW_EOI_CODE] = GIF_FRAME_DATA_ERROR, - [LZW_BAD_ICODE] = GIF_FRAME_DATA_ERROR, - [LZW_BAD_CODE] = GIF_FRAME_DATA_ERROR, - }; - return g_res[l_res]; + static const gif_result g_res[] = { + [LZW_OK] = GIF_OK, + [LZW_OK_EOD] = GIF_END_OF_FRAME, + [LZW_NO_MEM] = GIF_INSUFFICIENT_MEMORY, + [LZW_NO_DATA] = GIF_INSUFFICIENT_FRAME_DATA, + [LZW_EOI_CODE] = GIF_FRAME_DATA_ERROR, + [LZW_BAD_ICODE] = GIF_FRAME_DATA_ERROR, + [LZW_BAD_CODE] = GIF_FRAME_DATA_ERROR, + }; + return g_res[l_res]; } static void gif__record_previous_frame(gif_animation *gif) { - bool need_alloc = gif->prev_frame == NULL; - const uint32_t *frame_data; - uint32_t *prev_frame; + bool need_alloc = gif->prev_frame == NULL; + const uint32_t *frame_data; + uint32_t *prev_frame; - if (gif->decoded_frame == GIF_INVALID_FRAME || - gif->decoded_frame == gif->prev_index) { - /* No frame to copy, or already have this frame recorded. */ - return; - } + if (gif->decoded_frame == GIF_INVALID_FRAME || + gif->decoded_frame == gif->prev_index) { + /* No frame to copy, or already have this frame recorded. */ + return; + } - assert(gif->bitmap_callbacks.bitmap_get_buffer); - frame_data = (void *)gif->bitmap_callbacks.bitmap_get_buffer(gif->frame_image); - if (!frame_data) { - return; - } + assert(gif->bitmap_callbacks.bitmap_get_buffer); + frame_data = (void *)gif->bitmap_callbacks.bitmap_get_buffer(gif->frame_image); + if (!frame_data) { + return; + } - if (gif->prev_frame != NULL && - gif->width * gif->height > gif->prev_width * gif->prev_height) { - need_alloc = true; - } + if (gif->prev_frame != NULL && + gif->width * gif->height > gif->prev_width * gif->prev_height) { + need_alloc = true; + } - if (need_alloc) { - prev_frame = realloc(gif->prev_frame, - gif->width * gif->height * 4); - if (prev_frame == NULL) { - return; - } - } else { - prev_frame = gif->prev_frame; - } + if (need_alloc) { + prev_frame = realloc(gif->prev_frame, + gif->width * gif->height * 4); + if (prev_frame == NULL) { + return; + } + } else { + prev_frame = gif->prev_frame; + } - memcpy(prev_frame, frame_data, gif->width * gif->height * 4); + memcpy(prev_frame, frame_data, gif->width * gif->height * 4); - gif->prev_frame = prev_frame; - gif->prev_width = gif->width; - gif->prev_height = gif->height; - gif->prev_index = gif->decoded_frame; + gif->prev_frame = prev_frame; + gif->prev_width = gif->width; + gif->prev_height = gif->height; + gif->prev_index = gif->decoded_frame; } static gif_result gif__recover_previous_frame(const gif_animation *gif) { - const uint32_t *prev_frame = gif->prev_frame; - unsigned height = gif->height < gif->prev_height ? gif->height : gif->prev_height; - unsigned width = gif->width < gif->prev_width ? gif->width : gif->prev_width; - uint32_t *frame_data; + const uint32_t *prev_frame = gif->prev_frame; + unsigned height = gif->height < gif->prev_height ? gif->height : gif->prev_height; + unsigned width = gif->width < gif->prev_width ? gif->width : gif->prev_width; + uint32_t *frame_data; - if (prev_frame == NULL) { - return GIF_FRAME_DATA_ERROR; - } + if (prev_frame == NULL) { + return GIF_FRAME_DATA_ERROR; + } - assert(gif->bitmap_callbacks.bitmap_get_buffer); - frame_data = (void *)gif->bitmap_callbacks.bitmap_get_buffer(gif->frame_image); - if (!frame_data) { - return GIF_INSUFFICIENT_MEMORY; - } + assert(gif->bitmap_callbacks.bitmap_get_buffer); + frame_data = (void *)gif->bitmap_callbacks.bitmap_get_buffer(gif->frame_image); + if (!frame_data) { + return GIF_INSUFFICIENT_MEMORY; + } - for (unsigned y = 0; y < height; y++) { - memcpy(frame_data, prev_frame, width * 4); + for (unsigned y = 0; y < height; y++) { + memcpy(frame_data, prev_frame, width * 4); - frame_data += gif->width; - prev_frame += gif->prev_width; - } + frame_data += gif->width; + prev_frame += gif->prev_width; + } - return GIF_OK; + 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 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; + 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); - } + /* 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; + 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; + 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); + 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); - } + 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; + 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 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; + 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); - } + /* 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; + transparency_index = gif->frames[frame].transparency ? + gif->frames[frame].transparency_index : + GIF_NO_TRANSPARENCY; - frame_data += (offset_y * gif->width); + 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; - } - } + 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; - } + if (pixels == 0) { + ret = GIF_OK; + } - return ret; + 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) + 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; + 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); - } + 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; + return ret; } /** @@ -784,527 +783,527 @@ gif__decode(gif_animation *gif, */ static gif_result gif_internal_decode_frame(gif_animation *gif, - unsigned int frame, - bool clear_image) + unsigned int frame, + bool clear_image) { - gif_result err; - unsigned int index = 0; - unsigned char *gif_data, *gif_end; - int gif_bytes; - unsigned int width, height, offset_x, offset_y; - unsigned int flags, colour_table_size, interlace; - unsigned int *colour_table; - unsigned int *frame_data = 0; // Set to 0 for no warnings - unsigned int save_buffer_position; - unsigned int return_value = 0; + gif_result err; + unsigned int index = 0; + unsigned char *gif_data, *gif_end; + int gif_bytes; + unsigned int width, height, offset_x, offset_y; + unsigned int flags, colour_table_size, interlace; + unsigned int *colour_table; + unsigned int *frame_data = 0; // Set to 0 for no warnings + unsigned int save_buffer_position; + unsigned int return_value = 0; - /* Ensure this frame is supposed to be decoded */ - if (gif->frames[frame].display == false) { - return GIF_OK; - } + /* Ensure this frame is supposed to be decoded */ + if (gif->frames[frame].display == false) { + return GIF_OK; + } - /* Ensure the frame is in range to decode */ - if (frame > gif->frame_count_partial) { - return GIF_INSUFFICIENT_DATA; - } + /* Ensure the frame is in range to decode */ + if (frame > gif->frame_count_partial) { + return GIF_INSUFFICIENT_DATA; + } - /* done if frame is already decoded */ - if ((!clear_image) && - ((int)frame == gif->decoded_frame)) { - return GIF_OK; - } + /* done if frame is already decoded */ + if ((!clear_image) && + ((int)frame == gif->decoded_frame)) { + return GIF_OK; + } - /* Get the start of our frame data and the end of the GIF data */ - gif_data = gif->gif_data + gif->frames[frame].frame_pointer; - gif_end = gif->gif_data + gif->buffer_size; - gif_bytes = (gif_end - gif_data); + /* Get the start of our frame data and the end of the GIF data */ + gif_data = gif->gif_data + gif->frames[frame].frame_pointer; + gif_end = gif->gif_data + gif->buffer_size; + gif_bytes = (gif_end - gif_data); - /* - * Ensure there is a minimal amount of data to proceed. The shortest - * block of data is a 10-byte image descriptor + 1-byte gif trailer - */ - if (gif_bytes < 12) { - return GIF_INSUFFICIENT_FRAME_DATA; - } + /* + * Ensure there is a minimal amount of data to proceed. The shortest + * block of data is a 10-byte image descriptor + 1-byte gif trailer + */ + if (gif_bytes < 12) { + return GIF_INSUFFICIENT_FRAME_DATA; + } - /* Save the buffer position */ - save_buffer_position = gif->buffer_position; - gif->buffer_position = gif_data - gif->gif_data; + /* Save the buffer position */ + save_buffer_position = gif->buffer_position; + gif->buffer_position = gif_data - gif->gif_data; - /* Skip any extensions because they have allready been processed */ - if ((return_value = gif_skip_frame_extensions(gif)) != GIF_OK) { - goto gif_decode_frame_exit; - } - gif_data = (gif->gif_data + gif->buffer_position); - gif_bytes = (gif_end - gif_data); + /* Skip any extensions because they have already been processed */ + if ((return_value = gif_skip_frame_extensions(gif)) != GIF_OK) { + goto gif_decode_frame_exit; + } + gif_data = (gif->gif_data + gif->buffer_position); + gif_bytes = (gif_end - gif_data); - /* Ensure we have enough data for the 10-byte image descriptor + 1-byte - * gif trailer - */ - if (gif_bytes < 12) { - return_value = GIF_INSUFFICIENT_FRAME_DATA; - goto gif_decode_frame_exit; - } + /* Ensure we have enough data for the 10-byte image descriptor + 1-byte + * gif trailer + */ + if (gif_bytes < 12) { + return_value = GIF_INSUFFICIENT_FRAME_DATA; + goto gif_decode_frame_exit; + } - /* 10-byte Image Descriptor is: - * - * +0 CHAR Image Separator (0x2c) - * +1 SHORT Image Left Position - * +3 SHORT Image Top Position - * +5 SHORT Width - * +7 SHORT Height - * +9 CHAR __Packed Fields__ - * 1BIT Local Colour Table Flag - * 1BIT Interlace Flag - * 1BIT Sort Flag - * 2BITS Reserved - * 3BITS Size of Local Colour Table - */ - if (gif_data[0] != GIF_IMAGE_SEPARATOR) { - return_value = GIF_DATA_ERROR; - goto gif_decode_frame_exit; - } - offset_x = gif_data[1] | (gif_data[2] << 8); - offset_y = gif_data[3] | (gif_data[4] << 8); - width = gif_data[5] | (gif_data[6] << 8); - height = gif_data[7] | (gif_data[8] << 8); + /* 10-byte Image Descriptor is: + * + * +0 CHAR Image Separator (0x2c) + * +1 SHORT Image Left Position + * +3 SHORT Image Top Position + * +5 SHORT Width + * +7 SHORT Height + * +9 CHAR __Packed Fields__ + * 1BIT Local Colour Table Flag + * 1BIT Interlace Flag + * 1BIT Sort Flag + * 2BITS Reserved + * 3BITS Size of Local Colour Table + */ + if (gif_data[0] != GIF_IMAGE_SEPARATOR) { + return_value = GIF_DATA_ERROR; + goto gif_decode_frame_exit; + } + offset_x = gif_data[1] | (gif_data[2] << 8); + offset_y = gif_data[3] | (gif_data[4] << 8); + width = gif_data[5] | (gif_data[6] << 8); + height = gif_data[7] | (gif_data[8] << 8); - /* Boundary checking - shouldn't ever happen except unless the data has - * been modified since initialisation. - */ - if ((offset_x + width > gif->width) || - (offset_y + height > gif->height)) { - return_value = GIF_DATA_ERROR; - goto gif_decode_frame_exit; - } + /* Boundary checking - shouldn't ever happen except unless the data has + * been modified since initialisation. + */ + if ((offset_x + width > gif->width) || + (offset_y + height > gif->height)) { + return_value = GIF_DATA_ERROR; + 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; - } + /* 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); - interlace = flags & GIF_INTERLACE_MASK; + /* Decode the flags */ + flags = gif_data[9]; + colour_table_size = 2 << (flags & GIF_COLOUR_TABLE_SIZE_MASK); + interlace = flags & GIF_INTERLACE_MASK; - /* Advance data pointer to next block either colour table or image - * data. - */ - gif_data += 10; - gif_bytes = (gif_end - gif_data); + /* Advance data pointer to next block either colour table or image + * data. + */ + gif_data += 10; + gif_bytes = (gif_end - gif_data); - /* Set up the colour table */ - if (flags & GIF_COLOUR_TABLE_MASK) { - if (gif_bytes < (int)(3 * colour_table_size)) { - return_value = GIF_INSUFFICIENT_FRAME_DATA; - goto gif_decode_frame_exit; - } - colour_table = gif->local_colour_table; - if (!clear_image) { - for (index = 0; index < colour_table_size; index++) { - /* Gif colour map contents are r,g,b. - * - * We want to pack them bytewise into the - * colour table, such that the red component - * is in byte 0 and the alpha component is in - * byte 3. - */ - unsigned char *entry = - (unsigned char *) &colour_table[index]; + /* Set up the colour table */ + if (flags & GIF_COLOUR_TABLE_MASK) { + if (gif_bytes < (int)(3 * colour_table_size)) { + return_value = GIF_INSUFFICIENT_FRAME_DATA; + goto gif_decode_frame_exit; + } + colour_table = gif->local_colour_table; + if (!clear_image) { + for (index = 0; index < colour_table_size; index++) { + /* Gif colour map contents are r,g,b. + * + * We want to pack them bytewise into the + * colour table, such that the red component + * is in byte 0 and the alpha component is in + * byte 3. + */ + unsigned char *entry = + (unsigned char *) &colour_table[index]; - entry[0] = gif_data[0]; /* r */ - entry[1] = gif_data[1]; /* g */ - entry[2] = gif_data[2]; /* b */ - entry[3] = 0xff; /* a */ + entry[0] = gif_data[0]; /* r */ + entry[1] = gif_data[1]; /* g */ + entry[2] = gif_data[2]; /* b */ + entry[3] = 0xff; /* a */ - gif_data += 3; - } - } else { - gif_data += 3 * colour_table_size; - } - gif_bytes = (gif_end - gif_data); - } else { - colour_table = gif->global_colour_table; - } + gif_data += 3; + } + } else { + gif_data += 3 * colour_table_size; + } + gif_bytes = (gif_end - gif_data); + } else { + colour_table = gif->global_colour_table; + } - /* Ensure sufficient data remains */ - if (gif_bytes < 1) { - return_value = GIF_INSUFFICIENT_FRAME_DATA; - goto gif_decode_frame_exit; - } + /* Ensure sufficient data remains */ + if (gif_bytes < 1) { + return_value = GIF_INSUFFICIENT_FRAME_DATA; + goto gif_decode_frame_exit; + } - /* check for an end marker */ - if (gif_data[0] == GIF_TRAILER) { - return_value = GIF_OK; - goto gif_decode_frame_exit; - } + /* check for an end marker */ + if (gif_data[0] == GIF_TRAILER) { + return_value = GIF_OK; + goto gif_decode_frame_exit; + } - /* Get the frame data */ - assert(gif->bitmap_callbacks.bitmap_get_buffer); - frame_data = (void *)gif->bitmap_callbacks.bitmap_get_buffer(gif->frame_image); - if (!frame_data) { - return GIF_INSUFFICIENT_MEMORY; - } + /* Get the frame data */ + assert(gif->bitmap_callbacks.bitmap_get_buffer); + frame_data = (void *)gif->bitmap_callbacks.bitmap_get_buffer(gif->frame_image); + if (!frame_data) { + return GIF_INSUFFICIENT_MEMORY; + } - /* If we are clearing the image we just clear, if not decode */ - if (!clear_image) { - /* Ensure we have enough data for a 1-byte LZW code size + - * 1-byte gif trailer - */ - if (gif_bytes < 2) { - return_value = GIF_INSUFFICIENT_FRAME_DATA; - goto gif_decode_frame_exit; - } + /* If we are clearing the image we just clear, if not decode */ + if (!clear_image) { + /* Ensure we have enough data for a 1-byte LZW code size + + * 1-byte gif trailer + */ + if (gif_bytes < 2) { + return_value = GIF_INSUFFICIENT_FRAME_DATA; + goto gif_decode_frame_exit; + } - /* If we only have a 1-byte LZW code size + 1-byte gif trailer, - * we're finished - */ - if ((gif_bytes == 2) && (gif_data[1] == GIF_TRAILER)) { - return_value = GIF_OK; - goto gif_decode_frame_exit; - } + /* If we only have a 1-byte LZW code size + 1-byte gif trailer, + * we're finished + */ + if ((gif_bytes == 2) && (gif_data[1] == GIF_TRAILER)) { + return_value = GIF_OK; + goto gif_decode_frame_exit; + } - /* If the previous frame's disposal method requires we restore - * the background colour or this is the first frame, clear - * the frame data - */ - if ((frame == 0) || (gif->decoded_frame == GIF_INVALID_FRAME)) { - memset((char*)frame_data, - GIF_TRANSPARENT_COLOUR, - gif->width * gif->height * sizeof(int)); - gif->decoded_frame = frame; - /* The line below would fill the image with its - * background color, but because GIFs support - * transparency we likely wouldn't want to do that. */ - /* memset((char*)frame_data, colour_table[gif->background_index], gif->width * gif->height * sizeof(int)); */ - } else if ((frame != 0) && - (gif->frames[frame - 1].disposal_method == GIF_FRAME_CLEAR)) { - return_value = gif_internal_decode_frame(gif, - (frame - 1), - true); - if (return_value != GIF_OK) { - goto gif_decode_frame_exit; - } + /* If the previous frame's disposal method requires we restore + * the background colour or this is the first frame, clear + * the frame data + */ + if ((frame == 0) || (gif->decoded_frame == GIF_INVALID_FRAME)) { + memset((char*)frame_data, + GIF_TRANSPARENT_COLOUR, + gif->width * gif->height * sizeof(int)); + gif->decoded_frame = frame; + /* The line below would fill the image with its + * background color, but because GIFs support + * transparency we likely wouldn't want to do that. */ + /* memset((char*)frame_data, colour_table[gif->background_index], gif->width * gif->height * sizeof(int)); */ + } else if ((frame != 0) && + (gif->frames[frame - 1].disposal_method == GIF_FRAME_CLEAR)) { + return_value = gif_internal_decode_frame(gif, + (frame - 1), + true); + if (return_value != GIF_OK) { + goto gif_decode_frame_exit; + } - } else if ((frame != 0) && - (gif->frames[frame - 1].disposal_method == GIF_FRAME_RESTORE)) { - /* - * If the previous frame's disposal method requires we - * restore the previous image, restore our saved image. - */ - err = gif__recover_previous_frame(gif); - if (err != GIF_OK) { - /* see notes above on transparency - * vs. background color - */ - memset((char*)frame_data, - GIF_TRANSPARENT_COLOUR, - gif->width * gif->height * sizeof(int)); - } - } + } else if ((frame != 0) && + (gif->frames[frame - 1].disposal_method == GIF_FRAME_RESTORE)) { + /* + * If the previous frame's disposal method requires we + * restore the previous image, restore our saved image. + */ + err = gif__recover_previous_frame(gif); + if (err != GIF_OK) { + /* see notes above on transparency + * vs. background color + */ + memset((char*)frame_data, + GIF_TRANSPARENT_COLOUR, + gif->width * gif->height * sizeof(int)); + } + } - if (gif->frames[frame].disposal_method == GIF_FRAME_RESTORE) { - /* Store the previous frame for later restoration */ - gif__record_previous_frame(gif); - } + if (gif->frames[frame].disposal_method == GIF_FRAME_RESTORE) { + /* Store the previous frame for later restoration */ + gif__record_previous_frame(gif); + } - gif->decoded_frame = frame; - gif->buffer_position = (gif_data - gif->gif_data) + 1; + gif->decoded_frame = frame; + gif->buffer_position = (gif_data - gif->gif_data) + 1; - 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, - GIF_TRANSPARENT_COLOUR, - width * 4); - } else { - memset(frame_scanline, - colour_table[gif->background_index], - width * 4); - } - } - } - } + 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, + GIF_TRANSPARENT_COLOUR, + width * 4); + } else { + memset(frame_scanline, + colour_table[gif->background_index], + width * 4); + } + } + } + } gif_decode_frame_exit: - /* Check if we should test for optimisation */ - if (gif->frames[frame].virgin) { - if (gif->bitmap_callbacks.bitmap_test_opaque) { - gif->frames[frame].opaque = gif->bitmap_callbacks.bitmap_test_opaque(gif->frame_image); - } else { - gif->frames[frame].opaque = false; - } - gif->frames[frame].virgin = false; - } + /* Check if we should test for optimisation */ + if (gif->frames[frame].virgin) { + if (gif->bitmap_callbacks.bitmap_test_opaque) { + gif->frames[frame].opaque = gif->bitmap_callbacks.bitmap_test_opaque(gif->frame_image); + } else { + gif->frames[frame].opaque = false; + } + gif->frames[frame].virgin = false; + } - if (gif->bitmap_callbacks.bitmap_set_opaque) { - gif->bitmap_callbacks.bitmap_set_opaque(gif->frame_image, gif->frames[frame].opaque); - } + if (gif->bitmap_callbacks.bitmap_set_opaque) { + gif->bitmap_callbacks.bitmap_set_opaque(gif->frame_image, gif->frames[frame].opaque); + } - if (gif->bitmap_callbacks.bitmap_modified) { - gif->bitmap_callbacks.bitmap_modified(gif->frame_image); - } + if (gif->bitmap_callbacks.bitmap_modified) { + gif->bitmap_callbacks.bitmap_modified(gif->frame_image); + } - /* Restore the buffer position */ - gif->buffer_position = save_buffer_position; + /* Restore the buffer position */ + gif->buffer_position = save_buffer_position; - return return_value; + return return_value; } /* exported function documented in libnsgif.h */ void gif_create(gif_animation *gif, gif_bitmap_callback_vt *bitmap_callbacks) { - memset(gif, 0, sizeof(gif_animation)); - gif->bitmap_callbacks = *bitmap_callbacks; - gif->decoded_frame = GIF_INVALID_FRAME; - gif->prev_index = GIF_INVALID_FRAME; + memset(gif, 0, sizeof(gif_animation)); + gif->bitmap_callbacks = *bitmap_callbacks; + gif->decoded_frame = GIF_INVALID_FRAME; + gif->prev_index = GIF_INVALID_FRAME; } /* exported function documented in libnsgif.h */ gif_result gif_initialise(gif_animation *gif, size_t size, unsigned char *data) { - unsigned char *gif_data; - unsigned int index; - gif_result return_value; + unsigned char *gif_data; + unsigned int index; + gif_result return_value; - /* Initialize values */ - gif->buffer_size = size; - gif->gif_data = data; + /* Initialize values */ + gif->buffer_size = size; + gif->gif_data = data; - if (gif->lzw_ctx == NULL) { - lzw_result res = lzw_context_create( - (struct lzw_ctx **)&gif->lzw_ctx); - if (res != LZW_OK) { - return gif_error_from_lzw(res); - } - } + if (gif->lzw_ctx == NULL) { + lzw_result res = lzw_context_create( + (struct lzw_ctx **)&gif->lzw_ctx); + if (res != LZW_OK) { + return gif_error_from_lzw(res); + } + } - /* Check for sufficient data to be a GIF (6-byte header + 7-byte - * logical screen descriptor) - */ - if (gif->buffer_size < GIF_STANDARD_HEADER_SIZE) { - return GIF_INSUFFICIENT_DATA; - } + /* Check for sufficient data to be a GIF (6-byte header + 7-byte + * logical screen descriptor) + */ + if (gif->buffer_size < GIF_STANDARD_HEADER_SIZE) { + return GIF_INSUFFICIENT_DATA; + } - /* Get our current processing position */ - gif_data = gif->gif_data + gif->buffer_position; + /* Get our current processing position */ + gif_data = gif->gif_data + gif->buffer_position; - /* See if we should initialise the GIF */ - if (gif->buffer_position == 0) { - /* We want everything to be NULL before we start so we've no - * chance of freeing bad pointers (paranoia) - */ - gif->frame_image = NULL; - gif->frames = NULL; - gif->local_colour_table = NULL; - gif->global_colour_table = NULL; + /* See if we should initialise the GIF */ + if (gif->buffer_position == 0) { + /* We want everything to be NULL before we start so we've no + * chance of freeing bad pointers (paranoia) + */ + gif->frame_image = NULL; + gif->frames = NULL; + gif->local_colour_table = NULL; + gif->global_colour_table = NULL; - /* The caller may have been lazy and not reset any values */ - gif->frame_count = 0; - gif->frame_count_partial = 0; - gif->decoded_frame = GIF_INVALID_FRAME; + /* The caller may have been lazy and not reset any values */ + gif->frame_count = 0; + gif->frame_count_partial = 0; + gif->decoded_frame = GIF_INVALID_FRAME; - /* 6-byte GIF file header is: - * - * +0 3CHARS Signature ('GIF') - * +3 3CHARS Version ('87a' or '89a') - */ - if (strncmp((const char *) gif_data, "GIF", 3) != 0) { - return GIF_DATA_ERROR; - } - gif_data += 3; + /* 6-byte GIF file header is: + * + * +0 3CHARS Signature ('GIF') + * +3 3CHARS Version ('87a' or '89a') + */ + if (strncmp((const char *) gif_data, "GIF", 3) != 0) { + return GIF_DATA_ERROR; + } + gif_data += 3; - /* Ensure GIF reports version 87a or 89a */ - /* - if ((strncmp(gif_data, "87a", 3) != 0) && - (strncmp(gif_data, "89a", 3) != 0)) - LOG(("Unknown GIF format - proceeding anyway")); - */ - gif_data += 3; + /* Ensure GIF reports version 87a or 89a */ + /* + if ((strncmp(gif_data, "87a", 3) != 0) && + (strncmp(gif_data, "89a", 3) != 0)) + LOG(("Unknown GIF format - proceeding anyway")); + */ + gif_data += 3; - /* 7-byte Logical Screen Descriptor is: - * - * +0 SHORT Logical Screen Width - * +2 SHORT Logical Screen Height - * +4 CHAR __Packed Fields__ - * 1BIT Global Colour Table Flag - * 3BITS Colour Resolution - * 1BIT Sort Flag - * 3BITS Size of Global Colour Table - * +5 CHAR Background Colour Index - * +6 CHAR Pixel Aspect Ratio - */ - gif->width = gif_data[0] | (gif_data[1] << 8); - gif->height = gif_data[2] | (gif_data[3] << 8); - gif->global_colours = (gif_data[4] & GIF_COLOUR_TABLE_MASK); - gif->colour_table_size = (2 << (gif_data[4] & GIF_COLOUR_TABLE_SIZE_MASK)); - gif->background_index = gif_data[5]; - gif->aspect_ratio = gif_data[6]; - gif->loop_count = 1; - gif_data += 7; + /* 7-byte Logical Screen Descriptor is: + * + * +0 SHORT Logical Screen Width + * +2 SHORT Logical Screen Height + * +4 CHAR __Packed Fields__ + * 1BIT Global Colour Table Flag + * 3BITS Colour Resolution + * 1BIT Sort Flag + * 3BITS Size of Global Colour Table + * +5 CHAR Background Colour Index + * +6 CHAR Pixel Aspect Ratio + */ + gif->width = gif_data[0] | (gif_data[1] << 8); + gif->height = gif_data[2] | (gif_data[3] << 8); + gif->global_colours = (gif_data[4] & GIF_COLOUR_TABLE_MASK); + gif->colour_table_size = (2 << (gif_data[4] & GIF_COLOUR_TABLE_SIZE_MASK)); + gif->background_index = gif_data[5]; + gif->aspect_ratio = gif_data[6]; + gif->loop_count = 1; + gif_data += 7; - /* Some broken GIFs report the size as the screen size they - * were created in. As such, we detect for the common cases and - * set the sizes as 0 if they are found which results in the - * GIF being the maximum size of the frames. - */ - if (((gif->width == 640) && (gif->height == 480)) || - ((gif->width == 640) && (gif->height == 512)) || - ((gif->width == 800) && (gif->height == 600)) || - ((gif->width == 1024) && (gif->height == 768)) || - ((gif->width == 1280) && (gif->height == 1024)) || - ((gif->width == 1600) && (gif->height == 1200)) || - ((gif->width == 0) || (gif->height == 0)) || - ((gif->width > 2048) || (gif->height > 2048))) { - gif->width = 1; - gif->height = 1; - } + /* Some broken GIFs report the size as the screen size they + * were created in. As such, we detect for the common cases and + * set the sizes as 0 if they are found which results in the + * GIF being the maximum size of the frames. + */ + if (((gif->width == 640) && (gif->height == 480)) || + ((gif->width == 640) && (gif->height == 512)) || + ((gif->width == 800) && (gif->height == 600)) || + ((gif->width == 1024) && (gif->height == 768)) || + ((gif->width == 1280) && (gif->height == 1024)) || + ((gif->width == 1600) && (gif->height == 1200)) || + ((gif->width == 0) || (gif->height == 0)) || + ((gif->width > 2048) || (gif->height > 2048))) { + gif->width = 1; + gif->height = 1; + } - /* Allocate some data irrespective of whether we've got any - * colour tables. We always get the maximum size in case a GIF - * is lying to us. It's far better to give the wrong colours - * than to trample over some memory somewhere. - */ - gif->global_colour_table = calloc(GIF_MAX_COLOURS, sizeof(unsigned int)); - gif->local_colour_table = calloc(GIF_MAX_COLOURS, sizeof(unsigned int)); - if ((gif->global_colour_table == NULL) || - (gif->local_colour_table == NULL)) { - gif_finalise(gif); - return GIF_INSUFFICIENT_MEMORY; - } + /* Allocate some data irrespective of whether we've got any + * colour tables. We always get the maximum size in case a GIF + * is lying to us. It's far better to give the wrong colours + * than to trample over some memory somewhere. + */ + gif->global_colour_table = calloc(GIF_MAX_COLOURS, sizeof(unsigned int)); + gif->local_colour_table = calloc(GIF_MAX_COLOURS, sizeof(unsigned int)); + if ((gif->global_colour_table == NULL) || + (gif->local_colour_table == NULL)) { + gif_finalise(gif); + return GIF_INSUFFICIENT_MEMORY; + } - /* Set the first colour to a value that will never occur in - * reality so we know if we've processed it - */ - gif->global_colour_table[0] = GIF_PROCESS_COLOURS; + /* Set the first colour to a value that will never occur in + * reality so we know if we've processed it + */ + gif->global_colour_table[0] = GIF_PROCESS_COLOURS; - /* Check if the GIF has no frame data (13-byte header + 1-byte - * termination block) Although generally useless, the GIF - * specification does not expressly prohibit this - */ - if (gif->buffer_size == (GIF_STANDARD_HEADER_SIZE + 1)) { - if (gif_data[0] == GIF_TRAILER) { - return GIF_OK; - } else { - return GIF_INSUFFICIENT_DATA; - } - } + /* Check if the GIF has no frame data (13-byte header + 1-byte + * termination block) Although generally useless, the GIF + * specification does not expressly prohibit this + */ + if (gif->buffer_size == (GIF_STANDARD_HEADER_SIZE + 1)) { + if (gif_data[0] == GIF_TRAILER) { + return GIF_OK; + } else { + return GIF_INSUFFICIENT_DATA; + } + } - /* Initialise enough workspace for a frame */ - if ((gif->frames = (gif_frame *)malloc(sizeof(gif_frame))) == NULL) { - gif_finalise(gif); - return GIF_INSUFFICIENT_MEMORY; - } - gif->frame_holders = 1; + /* Initialise enough workspace for a frame */ + if ((gif->frames = (gif_frame *)malloc(sizeof(gif_frame))) == NULL) { + gif_finalise(gif); + return GIF_INSUFFICIENT_MEMORY; + } + gif->frame_holders = 1; - /* Remember we've done this now */ - gif->buffer_position = gif_data - gif->gif_data; - } + /* Remember we've done this now */ + gif->buffer_position = gif_data - gif->gif_data; + } - /* Do the colour map if we haven't already. As the top byte is always - * 0xff or 0x00 depending on the transparency we know if it's been - * filled in. - */ - if (gif->global_colour_table[0] == GIF_PROCESS_COLOURS) { - /* Check for a global colour map signified by bit 7 */ - if (gif->global_colours) { - if (gif->buffer_size < (gif->colour_table_size * 3 + GIF_STANDARD_HEADER_SIZE)) { - return GIF_INSUFFICIENT_DATA; - } - for (index = 0; index < gif->colour_table_size; index++) { - /* Gif colour map contents are r,g,b. - * - * We want to pack them bytewise into the - * colour table, such that the red component - * is in byte 0 and the alpha component is in - * byte 3. - */ - unsigned char *entry = (unsigned char *) &gif-> - global_colour_table[index]; + /* Do the colour map if we haven't already. As the top byte is always + * 0xff or 0x00 depending on the transparency we know if it's been + * filled in. + */ + if (gif->global_colour_table[0] == GIF_PROCESS_COLOURS) { + /* Check for a global colour map signified by bit 7 */ + if (gif->global_colours) { + if (gif->buffer_size < (gif->colour_table_size * 3 + GIF_STANDARD_HEADER_SIZE)) { + return GIF_INSUFFICIENT_DATA; + } + for (index = 0; index < gif->colour_table_size; index++) { + /* Gif colour map contents are r,g,b. + * + * We want to pack them bytewise into the + * colour table, such that the red component + * is in byte 0 and the alpha component is in + * byte 3. + */ + unsigned char *entry = (unsigned char *) &gif-> + global_colour_table[index]; - entry[0] = gif_data[0]; /* r */ - entry[1] = gif_data[1]; /* g */ - entry[2] = gif_data[2]; /* b */ - entry[3] = 0xff; /* a */ + entry[0] = gif_data[0]; /* r */ + entry[1] = gif_data[1]; /* g */ + entry[2] = gif_data[2]; /* b */ + entry[3] = 0xff; /* a */ - gif_data += 3; - } - gif->buffer_position = (gif_data - gif->gif_data); - } else { - /* Create a default colour table with the first two - * colours as black and white - */ - unsigned int *entry = gif->global_colour_table; + gif_data += 3; + } + gif->buffer_position = (gif_data - gif->gif_data); + } else { + /* Create a default colour table with the first two + * colours as black and white + */ + unsigned int *entry = gif->global_colour_table; - entry[0] = 0x00000000; - /* Force Alpha channel to opaque */ - ((unsigned char *) entry)[3] = 0xff; + entry[0] = 0x00000000; + /* Force Alpha channel to opaque */ + ((unsigned char *) entry)[3] = 0xff; - entry[1] = 0xffffffff; - } - } + entry[1] = 0xffffffff; + } + } - /* Repeatedly try to initialise frames */ - while ((return_value = gif_initialise_frame(gif)) == GIF_WORKING); + /* Repeatedly try to initialise frames */ + while ((return_value = gif_initialise_frame(gif)) == GIF_WORKING); - /* If there was a memory error tell the caller */ - if ((return_value == GIF_INSUFFICIENT_MEMORY) || - (return_value == GIF_DATA_ERROR)) { - return return_value; - } + /* If there was a memory error tell the caller */ + if ((return_value == GIF_INSUFFICIENT_MEMORY) || + (return_value == GIF_DATA_ERROR)) { + return return_value; + } - /* If we didn't have some frames then a GIF_INSUFFICIENT_DATA becomes a - * GIF_INSUFFICIENT_FRAME_DATA - */ - if ((return_value == GIF_INSUFFICIENT_DATA) && - (gif->frame_count_partial > 0)) { - return GIF_INSUFFICIENT_FRAME_DATA; - } + /* If we didn't have some frames then a GIF_INSUFFICIENT_DATA becomes a + * GIF_INSUFFICIENT_FRAME_DATA + */ + if ((return_value == GIF_INSUFFICIENT_DATA) && + (gif->frame_count_partial > 0)) { + return GIF_INSUFFICIENT_FRAME_DATA; + } - /* Return how many we got */ - return return_value; + /* Return how many we got */ + return return_value; } /* exported function documented in libnsgif.h */ gif_result gif_decode_frame(gif_animation *gif, unsigned int frame) { - return gif_internal_decode_frame(gif, frame, false); + return gif_internal_decode_frame(gif, frame, false); } /* exported function documented in libnsgif.h */ void gif_finalise(gif_animation *gif) { - /* Release all our memory blocks */ - if (gif->frame_image) { - assert(gif->bitmap_callbacks.bitmap_destroy); - gif->bitmap_callbacks.bitmap_destroy(gif->frame_image); - } + /* Release all our memory blocks */ + if (gif->frame_image) { + assert(gif->bitmap_callbacks.bitmap_destroy); + gif->bitmap_callbacks.bitmap_destroy(gif->frame_image); + } - gif->frame_image = NULL; - free(gif->frames); - gif->frames = NULL; - free(gif->local_colour_table); - gif->local_colour_table = NULL; - free(gif->global_colour_table); - gif->global_colour_table = NULL; + gif->frame_image = NULL; + free(gif->frames); + gif->frames = NULL; + free(gif->local_colour_table); + gif->local_colour_table = NULL; + free(gif->global_colour_table); + gif->global_colour_table = NULL; - free(gif->prev_frame); - gif->prev_frame = NULL; + free(gif->prev_frame); + gif->prev_frame = NULL; - lzw_context_destroy(gif->lzw_ctx); - gif->lzw_ctx = NULL; + lzw_context_destroy(gif->lzw_ctx); + gif->lzw_ctx = NULL; } diff --git a/libvips/foreign/libnsgif/lzw.c b/libvips/foreign/libnsgif/lzw.c index 4b521b68..c865b8d9 100644 --- a/libvips/foreign/libnsgif/lzw.c +++ b/libvips/foreign/libnsgif/lzw.c @@ -224,18 +224,39 @@ static inline lzw_result lzw__read_code( /** - * Clear LZW code table. + * Handle clear code. * - * \param[in] ctx LZW reading context, updated. + * \param[in] ctx LZW reading context, updated. + * \param[out] code_out Returns next code after a clear code. * \return LZW_OK or error code. */ -static inline void lzw__clear_table( - struct lzw_ctx *ctx) +static inline lzw_result lzw__handle_clear( + struct lzw_ctx *ctx, + uint32_t *code_out) { + 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; + } + + *code_out = code; + return LZW_OK; } @@ -248,6 +269,8 @@ lzw_result lzw_decode_init( uint8_t minimum_code_size) { struct lzw_table_entry *table = ctx->table; + lzw_result res; + uint32_t code; if (minimum_code_size >= LZW_CODE_MAX) { return LZW_BAD_ICODE; @@ -276,8 +299,19 @@ lzw_result lzw_decode_init( table[i].count = 1; } - lzw__clear_table(ctx); - ctx->prev_code = ctx->clear_code; + res = lzw__handle_clear(ctx, &code); + if (res != LZW_OK) { + return res; + } + + /* 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; + + /* Add code to context for immediate output. */ + ctx->output_code = code; + ctx->output_left = 1; return LZW_OK; } @@ -345,27 +379,27 @@ static inline lzw_result lzw__decode(struct lzw_ctx *ctx, return LZW_BAD_CODE; } 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; - } + res = lzw__handle_clear(ctx, &code); + if (res != LZW_OK) { + return res; } - *used += write_pixels(ctx, output, length, *used, code, - ctx->table[code].count); + } else 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 (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->prev_code_first = ctx->table[code].first; ctx->prev_code_count = ctx->table[code].count; @@ -428,20 +462,9 @@ static inline uint32_t lzw__write_pixels(struct lzw_ctx *ctx, 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, + const uint8_t *restrict *const restrict data, uint32_t *restrict used) { *used = 0; diff --git a/libvips/foreign/libnsgif/lzw.h b/libvips/foreign/libnsgif/lzw.h index c442cff6..a227163b 100644 --- a/libvips/foreign/libnsgif/lzw.h +++ b/libvips/foreign/libnsgif/lzw.h @@ -76,21 +76,6 @@ lzw_result lzw_decode_init( uint32_t compressed_data_pos, uint8_t minimum_code_size); -/** - * Read a single LZW code and write into 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(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. * @@ -103,7 +88,7 @@ lzw_result lzw_decode(struct lzw_ctx *ctx, * \return LZW_OK on success, or appropriate error code otherwise. */ lzw_result lzw_decode_continuous(struct lzw_ctx *ctx, - const uint8_t ** const data, + const uint8_t *restrict *const restrict data, uint32_t *restrict used); /** diff --git a/libvips/foreign/libnsgif/patches/no-input-modify.patch b/libvips/foreign/libnsgif/patches/no-input-modify.patch index f358f307..a7a8e622 100644 --- a/libvips/foreign/libnsgif/patches/no-input-modify.patch +++ b/libvips/foreign/libnsgif/patches/no-input-modify.patch @@ -1,23 +1,13 @@ ---- libnsgif-orig.c 2021-02-28 14:10:41.818557190 +0000 -+++ libnsgif.c 2021-02-28 14:11:55.942285930 +0000 -@@ -435,20 +435,15 @@ - block_size = gif_data[0] + 1; - /* Check if the frame data runs off the end of the file */ - if ((int)(gif_bytes - block_size) < 0) { -- /* Try to recover by signaling the end of the gif. -- * Once we get garbage data, there is no logical way to -- * determine where the next frame is. It's probably -- * better to partially load the gif than not at all. -- */ -- if (gif_bytes >= 2) { -- gif_data[0] = 0; -- gif_data[1] = GIF_TRAILER; -- gif_bytes = 1; -- ++gif_data; -- break; -- } else { -- return GIF_INSUFFICIENT_FRAME_DATA; -- } +--- libnsgif.c.orig 2021-04-24 18:33:02.757323861 +0100 ++++ libnsgif.c 2021-04-24 18:35:14.659860190 +0100 +@@ -424,20 +424,15 @@ + block_size = gif_data[0] + 1; + /* Check if the frame data runs off the end of the file */ + if ((int)(gif_bytes - block_size) < 0) { +- /* Try to recover by signaling the end of the gif. +- * Once we get garbage data, there is no logical way to +- * determine where the next frame is. It's probably +- * better to partially load the gif than not at all. + /* jcupitt 15/9/19 + * + * There was code here to set a TRAILER tag. But this @@ -25,8 +15,17 @@ + * libvips, where buffers can be mmaped read only files. + * + * Instead, just signal insufficient frame data. -+ */ + */ +- if (gif_bytes >= 2) { +- gif_data[0] = 0; +- gif_data[1] = GIF_TRAILER; +- gif_bytes = 1; +- ++gif_data; +- break; +- } else { +- return GIF_INSUFFICIENT_FRAME_DATA; +- } + return GIF_INSUFFICIENT_FRAME_DATA; - } else { - gif_bytes -= block_size; - gif_data += block_size; + } else { + gif_bytes -= block_size; + gif_data += block_size; diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index de2b38c2..fd3e3728 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -238,6 +238,8 @@ void vips__heif_image_print( struct heif_image *img ); extern const char *vips__jp2k_suffs[]; +extern const char *vips__jxl_suffs[]; + #ifdef __cplusplus } #endif /*__cplusplus*/ diff --git a/libvips/foreign/ppmsave.c b/libvips/foreign/ppmsave.c index bf2bf570..8e719c6e 100644 --- a/libvips/foreign/ppmsave.c +++ b/libvips/foreign/ppmsave.c @@ -327,6 +327,7 @@ vips_foreign_save_ppm_build( VipsObject *object ) */ g_ascii_dtostr( buf, G_ASCII_DTOSTR_BUF_SIZE, scale ); vips_target_writes( ppm->target, buf ); + vips_target_writes( ppm->target, "\n" ); } break; diff --git a/libvips/include/vips/foreign.h b/libvips/include/vips/foreign.h index 7a86f2f9..15e114c0 100644 --- a/libvips/include/vips/foreign.h +++ b/libvips/include/vips/foreign.h @@ -689,6 +689,13 @@ int vips_jp2ksave_buffer( VipsImage *in, void **buf, size_t *len, ... ) int vips_jp2ksave_target( VipsImage *in, VipsTarget *target, ... ) __attribute__((sentinel)); +int vips_jxlload_source( VipsSource *source, VipsImage **out, ... ) + __attribute__((sentinel)); +int vips_jxlload_buffer( void *buf, size_t len, VipsImage **out, ... ) + __attribute__((sentinel)); +int vips_jxlload( const char *filename, VipsImage **out, ... ) + __attribute__((sentinel)); + /** * VipsForeignDzLayout: * @VIPS_FOREIGN_DZ_LAYOUT_DZ: use DeepZoom directory layout