From c7f69718da7f4d1090f4af73906538d33f288b56 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 1 Apr 2021 09:21:41 +0100 Subject: [PATCH 01/23] start adding jxl --- configure.ac | 23 + libvips/foreign/Makefile.am | 1 + libvips/foreign/foreign.c | 10 + libvips/foreign/jp2kload.c | 6 +- libvips/foreign/jxlload.cpp | 1176 ++++++++++++++++++++++++++++++++ libvips/include/vips/foreign.h | 7 + 6 files changed, 1222 insertions(+), 1 deletion(-) create mode 100644 libvips/foreign/jxlload.cpp diff --git a/configure.ac b/configure.ac index 9f906b8c..28a631f1 100644 --- a/configure.ac +++ b/configure.ac @@ -770,6 +770,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 >= 0.3.7, + [AC_DEFINE(HAVE_LIBJXL,1, + [define if you have libjxl >=2.2 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], @@ -1442,6 +1464,7 @@ TIFF load/save with libtiff: $with_tiff image pyramid save: $with_gsf (requires libgsf-1 1.14.26 or later) HEIC/AVIF load/save with libheif: $with_heif +JXL load/save with libjxl: $with_libjxl WebP load/save with libwebp: $with_libwebp (requires libwebp, libwebpmux, libwebpdemux 0.6.0 or later) PDF load with PDFium: $with_pdfium diff --git a/libvips/foreign/Makefile.am b/libvips/foreign/Makefile.am index afe30463..a51a056b 100644 --- a/libvips/foreign/Makefile.am +++ b/libvips/foreign/Makefile.am @@ -19,6 +19,7 @@ libforeign_la_SOURCES = \ foreign.c \ heifload.c \ heifsave.c \ + jxlload.cpp \ jp2kload.c \ jp2ksave.c \ jpeg2vips.c \ diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index 9cb0e876..d1cede50 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -2183,6 +2183,10 @@ 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_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 +2260,12 @@ 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(); +#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/jxlload.cpp b/libvips/foreign/jxlload.cpp new file mode 100644 index 00000000..6da7683b --- /dev/null +++ b/libvips/foreign/jxlload.cpp @@ -0,0 +1,1176 @@ +/* load jpeg-xl + * + * 18/3/20 + * - from jxlload.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 +#include + +#include "pforeign.h" + +#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; + + /* Decompress state. + */ + JxlThreadParallelRunner *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; + +/* We need C linkage for this. + */ +extern "C" { +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 ); + + G_OBJECT_CLASS( vips_foreign_load_jxl_parent_class )-> + dispose( gobject ); +} + +static int +vips_foreign_load_jxl_build( VipsObject *object ) +{ + VipsObjectClass *klass = VIPS_OBJECT_GET_CLASS( object ); + VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) object; + + JxlDecoderStatus status; + +#ifdef DEBUG + printf( "vips_foreign_load_jxl_build:\n" ); +#endif /*DEBUG*/ + + jxl->runner = JxlThreadParallelRunnerCreate( nullptr, + vips_concurrency_get() ); + jxl->decoder = JxlDecoderCreate( nullptr ); + + if( JxlDecoderSubscribeEvents( jxl->decoder, + JXL_DEC_BASIC_INFO | + JXL_DEC_COLOR_ENCODING | + JXL_DEC_FULL_IMAGE ) != JXL_DEC_SUCCESS ) { + vips_error( klass->nickname, + "%s", _( "JxlDecoderSubscribeEvents failed" ) ); + return( -1 ); + } + if( JxlDecoderSetParallelRunner( jxl->decoder, + JxlThreadParallelRunner, jxl->runner ) != JXL_DEC_SUCCESS ) { + vips_error( klass->nickname, + "%s", _( "JxlDecoderSetParallelRunner failed" ) ); + return( -1 ); + } + + if( VIPS_OBJECT_CLASS( vips_foreign_load_jxl_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static int +vips_foreign_load_jxl_set_header( VipsForeignLoadJxl *jxl, VipsImage *out ) +{ + VipsObjectClass *klass = VIPS_OBJECT_GET_CLASS( jxl ); + opj_image_comp_t *first = &jxl->image->comps[0]; + + VipsBandFormat format; + VipsInterpretation interpretation; + + /* OpenJPEG only supports up to 31 bpp. Treat it as 32. + */ + if( first->prec <= 8 ) + format = first->sgnd ? VIPS_FORMAT_CHAR : VIPS_FORMAT_UCHAR; + else if( first->prec <= 16 ) + format = first->sgnd ? VIPS_FORMAT_SHORT : VIPS_FORMAT_USHORT; + else + format = first->sgnd ? VIPS_FORMAT_INT : VIPS_FORMAT_UINT; + + switch( jxl->image->color_space ) { + case OPJ_CLRSPC_SYCC: + case OPJ_CLRSPC_EYCC: + /* Map these to RGB. + */ + interpretation = vips_format_sizeof( format ) == 1 ? + VIPS_INTERPRETATION_sRGB : + VIPS_INTERPRETATION_RGB16; + jxl->ycc_to_rgb = TRUE; + break; + + case OPJ_CLRSPC_GRAY: + interpretation = vips_format_sizeof( format ) == 1 ? + VIPS_INTERPRETATION_B_W : + VIPS_INTERPRETATION_GREY16; + break; + + case OPJ_CLRSPC_SRGB: + interpretation = vips_format_sizeof( format ) == 1 ? + VIPS_INTERPRETATION_sRGB : + VIPS_INTERPRETATION_RGB16; + break; + + case OPJ_CLRSPC_CMYK: + interpretation = VIPS_INTERPRETATION_CMYK; + break; + + case OPJ_CLRSPC_UNSPECIFIED: + /* Try to guess something sensible. + */ + if( jxl->image->numcomps < 3 ) + interpretation = vips_format_sizeof( format ) == 1 ? + VIPS_INTERPRETATION_B_W : + VIPS_INTERPRETATION_GREY16; + else + interpretation = vips_format_sizeof( format ) == 1 ? + VIPS_INTERPRETATION_sRGB : + VIPS_INTERPRETATION_RGB16; + + /* Unspecified with three bands and subsampling on bands 2 and + * 3 is usually YCC. + */ + if( jxl->image->numcomps == 3 && + jxl->image->comps[0].dx == 1 && + jxl->image->comps[0].dy == 1 && + jxl->image->comps[1].dx > 1 && + jxl->image->comps[1].dy > 1 && + jxl->image->comps[2].dx > 1 && + jxl->image->comps[2].dy > 1) + jxl->ycc_to_rgb = TRUE; + + break; + + default: + vips_error( klass->nickname, + _( "unsupported colourspace %d" ), + jxl->image->color_space ); + return( -1 ); + } + + /* Even though this is a tiled reader, we hint thinstrip since with + * the cache 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, + first->w, first->h, jxl->image->numcomps, format, + VIPS_CODING_NONE, interpretation, 1.0, 1.0 ); + + /* openjpeg allows left and top of the coordinate grid to be + * non-zero. These are always in unshrunk coordinates. + */ + out->Xoffset = + -VIPS_ROUND_INT( (double) jxl->image->x0 / jxl->shrink ); + out->Yoffset = + -VIPS_ROUND_INT( (double) jxl->image->y0 / jxl->shrink ); + + if( jxl->image->icc_profile_buf && + jxl->image->icc_profile_len > 0 ) + vips_image_set_blob_copy( out, VIPS_META_ICC_NAME, + jxl->image->icc_profile_buf, + jxl->image->icc_profile_len ); + + /* Map number of layers in image to pages. + */ + if( jxl->info && + jxl->info->m_default_tile_info.tccp_info ) + vips_image_set_int( out, VIPS_META_N_PAGES, + jxl->info->m_default_tile_info.tccp_info-> + numresolutions ); + + return( 0 ); +} + +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 ); +} + +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: + g_assert_not_reached(); + } +} + +static JxlDecoderStatus +vips_foreign_load_jxl_process( VipsForeignLoadJxl *jxl ) +{ + JxlDecoderStatus status; + + while( (status = JxlDecoderProcessInput( jx->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 ) ) + return( JXL_DEC_ERROR ); + JxlDecoderSetInput( jxl->decoder, + jxl->input_buffer, jxl->bytes_remaining ); + } + + printf( "vips_foreign_load_jxl_process: seen " ); + vips_foreign_load_jxl_print_status( status ); + + return( status ); +} + + +static int +vips_foreign_load_jxl_header( VipsForeignLoad *load ) +{ + VipsObjectClass *klass = VIPS_OBJECT_GET_CLASS( load ); + VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) load; + +#ifdef DEBUG + printf( "vips_foreign_load_jxl_header:\n" ); +#endif /*DEBUG*/ + + if( vips_foreign_load_jxl_fill_input( jxl, 0 ) ) + return( -1 ); + JxlDecoderSetInput( jx->decoder, + jxl->input_buffer, jxl->bytes_remaining ); + + /* Read to the end of the header. + */ + do { + JxlDecoderStatus status; + + status = vips_foreign_load_jxl_process( jxl ); + if( status == JXL_DEC_ERROR ) + return( -1 ); + } while( status != JXL_DEC_COLOR_ENCODING ); + + JxlDecoderGetBasicInfo( jxl->decoder, &jxl->info ); + + + jxl->format = vips_foreign_load_jxl_get_format( jxl->source ); + vips_source_rewind( jxl->source ); + if( !(jxl->codec = opj_create_decompress( jxl->format )) ) + return( -1 ); + + vips_foreign_load_jxl_attach_handlers( jxl, jxl->codec ); + + jxl->shrink = 1 << jxl->page; + jxl->parameters.cp_reduce = jxl->page; + if( !opj_setup_decoder( jxl->codec, &jxl->parameters ) ) + return( -1 ); + +#ifdef HAVE_LIBJXL_THREADING + /* Use eg. VIPS_CONCURRENCY etc. to set n-cpus, if this openjpeg has + * stable support. + */ + opj_codec_set_threads( jxl->codec, vips_concurrency_get() ); +#endif /*HAVE_LIBJXL_THREADING*/ + + if( !opj_read_header( jxl->stream, jxl->codec, &jxl->image ) ) + return( -1 ); + if( !(jxl->info = opj_get_cstr_info( jxl->codec )) ) + return( -1 ); + +#ifdef DEBUG + vips_foreign_load_jxl_print( jxl ); +#endif /*DEBUG*/ + + /* We only allow images where all components have the same format. + */ + if( jxl->image->numcomps > MAX_BANDS ) { + vips_error( klass->nickname, + "%s", _( "too many image bands" ) ); + return( -1 ); + } + if( jxl->image->numcomps == 0 ) { + vips_error( klass->nickname, + "%s", _( "no image components" ) ); + return( -1 ); + } + first = &jxl->image->comps[0]; + for( i = 1; i < jxl->image->numcomps; i++ ) { + opj_image_comp_t *this = &jxl->image->comps[i]; + + if( this->x0 != first->x0 || + this->y0 != first->y0 || + this->w * this->dx != first->w * first->dx || + this->h * this->dy != first->h * first->dy || + this->resno_decoded != first->resno_decoded || + this->factor != first->factor ) { + vips_error( klass->nickname, + "%s", _( "components differ in geometry" ) ); + return( -1 ); + } + + if( this->prec != first->prec || + this->bpp != first->bpp || + this->sgnd != first->sgnd ) { + vips_error( klass->nickname, + "%s", _( "components differ in precision" ) ); + return( -1 ); + } + + /* If dx/dy are not 1, we'll need to upsample components during + * tile packing. + */ + if( this->dx != first->dx || + this->dy != first->dy || + first->dx != 1 || + first->dy != 1 ) + jxl->upsample = TRUE; + } + + 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 ); +} + +#define PACK( TYPE ) { \ + TYPE *tq = (TYPE *) q; \ + \ + for( x = 0; x < length; x++ ) { \ + for( i = 0; i < b; i++ ) \ + tq[i] = planes[i][x]; \ + \ + tq += b; \ + } \ +} + +#define PACK_UPSAMPLE( TYPE ) { \ + TYPE *tq = (TYPE *) q; \ + \ + for( x = 0; x < length; x++ ) { \ + for( i = 0; i < b; i++ ) { \ + int dx = jxl->image->comps[i].dx; \ + int pixel = planes[i][x / dx]; \ + \ + tq[i] = pixel; \ + } \ + \ + tq += b; \ + } \ +} + +/* Pack the set of openjpeg components into a libvips region. left/top are the + * offsets into the tile in pixel coordinates where we should start reading. + */ +static void +vips_foreign_load_jxl_pack( VipsForeignLoadJxl *jxl, + VipsImage *image, VipsPel *q, + int left, int top, int length ) +{ + int *planes[MAX_BANDS]; + int b = jxl->image->numcomps; + + int x, i; + + for( i = 0; i < b; i++ ) { + opj_image_comp_t *comp = &jxl->image->comps[i]; + + planes[i] = comp->data + (top / comp->dy) * comp->w + + (left / comp->dx); + } + + if( jxl->upsample ) + switch( image->BandFmt ) { + case VIPS_FORMAT_CHAR: + case VIPS_FORMAT_UCHAR: + PACK_UPSAMPLE( unsigned char ); + break; + + case VIPS_FORMAT_SHORT: + case VIPS_FORMAT_USHORT: + PACK_UPSAMPLE( unsigned short ); + break; + + case VIPS_FORMAT_INT: + case VIPS_FORMAT_UINT: + PACK_UPSAMPLE( unsigned int ); + break; + + default: + g_assert_not_reached(); + break; + } + else + /* Fast no-upsample path. + */ + switch( image->BandFmt ) { + case VIPS_FORMAT_CHAR: + case VIPS_FORMAT_UCHAR: + PACK( unsigned char ); + break; + + case VIPS_FORMAT_SHORT: + case VIPS_FORMAT_USHORT: + PACK( unsigned short ); + break; + + case VIPS_FORMAT_INT: + case VIPS_FORMAT_UINT: + PACK( unsigned int ); + break; + + default: + g_assert_not_reached(); + break; + } +} + +/* ycc->rgb coversion adapted from openjpeg src/bin/common/color.c + * + * See also https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion + */ +#define YCC_TO_RGB( TYPE ) { \ + TYPE *tq = (TYPE *) q; \ + \ + for( x = 0; x < length; x++ ) { \ + int y = tq[0]; \ + int cb = tq[1] - offset; \ + int cr = tq[2] - offset; \ + \ + int r, g, b; \ + \ + r = y + (int)(1.402 * (float)cr); \ + tq[0] = VIPS_CLIP( 0, r, upb ); \ + \ + g = y - (int)(0.344 * (float)cb + 0.714 * (float)cr); \ + tq[1] = VIPS_CLIP( 0, g, upb ); \ + \ + b = y + (int)(1.772 * (float)cb); \ + tq[2] = VIPS_CLIP( 0, b, upb ); \ + \ + tq += 3; \ + } \ +} + +/* YCC->RGB for a line of pels. + */ +static void +vips_foreign_load_jxl_ycc_to_rgb( VipsForeignLoadJxl *jxl, + VipsPel *q, int length ) +{ + VipsForeignLoad *load = (VipsForeignLoad *) jxl; + int prec = jxl->image->comps[0].prec; + int offset = 1 << (prec - 1); + int upb = (1 << prec) - 1; + + int x; + + switch( load->out->BandFmt ) { + case VIPS_FORMAT_CHAR: + case VIPS_FORMAT_UCHAR: + YCC_TO_RGB( unsigned char ); + break; + + case VIPS_FORMAT_SHORT: + case VIPS_FORMAT_USHORT: + YCC_TO_RGB( unsigned short ); + break; + + case VIPS_FORMAT_INT: + case VIPS_FORMAT_UINT: + YCC_TO_RGB( unsigned int ); + break; + + default: + g_assert_not_reached(); + break; + } +} + +/* Loop over the output region, painting in tiles from the file. + */ +static int +vips_foreign_load_jxl_generate( VipsRegion *out, + void *seq, void *a, void *b, gboolean *stop ) +{ + VipsForeignLoad *load = (VipsForeignLoad *) a; + VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) load; + VipsRect *r = &out->valid; + + /* jxl get smaller with the layer size. + */ + int tile_width = VIPS_ROUND_UINT( + (double) jxl->info->tdx / jxl->shrink ); + int tile_height = VIPS_ROUND_UINT( + (double) jxl->info->tdy / jxl->shrink ); + + /* ... so tiles_across is always the same. + */ + int tiles_across = jxl->info->tw; + + int x, y, z; + +#ifdef DEBUG_VERBOSE + printf( "vips_foreign_load_jxl_generate: " + "left = %d, top = %d, width = %d, height = %d\n", + r->left, r->top, r->width, r->height ); +#endif /*DEBUG_VERBOSE*/ + + /* If openjpeg has flagged an error, the library is not in a known + * state and it's not safe to call again. + */ + if( jxl->n_errors ) + return( 0 ); + + y = 0; + while( y < r->height ) { + VipsRect tile, hit; + + /* Not necessary, but it stops static analyzers complaining + * about a used-before-set. + */ + hit.height = 0; + + x = 0; + while( x < r->width ) { + /* Tile the xy falls in, in tile numbers. + */ + int tx = (r->left + x) / tile_width; + int ty = (r->top + y) / tile_height; + + /* Pixel coordinates of the tile that xy falls in. + */ + int xs = tx * tile_width; + int ys = ty * tile_height; + + int tile_index = ty * tiles_across + tx; + + /* Fetch the tile. + */ +#ifdef DEBUG_VERBOSE + printf( " fetch tile %d\n", tile_index ); +#endif /*DEBUG_VERBOSE*/ + if( !opj_get_decoded_tile( jxl->codec, + jxl->stream, jxl->image, tile_index ) ) + return( -1 ); + + /* Intersect tile with request to get pixels we need + * to copy out. + */ + tile.left = xs; + tile.top = ys; + tile.width = tile_width; + tile.height = tile_height; + vips_rect_intersectrect( &tile, r, &hit ); + + /* Unpack hit pixels to buffer in vips layout. + */ + for( z = 0; z < hit.height; z++ ) { + VipsPel *q = VIPS_REGION_ADDR( out, + hit.left, hit.top + z ); + + vips_foreign_load_jxl_pack( jxl, + out->im, q, + hit.left - tile.left, + hit.top - tile.top + z, + hit.width ); + + if( jxl->ycc_to_rgb ) + vips_foreign_load_jxl_ycc_to_rgb( jxl, + q, hit.width ); + } + + x += hit.width; + } + + /* This will be the same for all tiles in the row we've just + * done. + */ + y += hit.height; + } + + if( load->fail && + jxl->n_errors > 0 ) + return( -1 ); + + return( 0 ); +} + +static int +vips_foreign_load_jxl_load( VipsForeignLoad *load ) +{ + VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) load; + + /* jxl tiles get smaller with the layer size, but we don't want tiny + * tiles for the libvips tile cache, so leave them at the base size. + */ + int tile_width = jxl->info->tdx; + int tile_height = jxl->info->tdy; + int tiles_across = jxl->info->tw; + + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( load ), 3 ); + +#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 ); + + if( vips_image_generate( t[0], + NULL, vips_foreign_load_jxl_generate, NULL, jxl, NULL ) ) + return( -1 ); + + /* Copy to out, adding a cache. Enough tiles for two complete + * rows, plus 50%. + */ + if( vips_tilecache( t[0], &t[1], + "tile_width", tile_width, + "tile_height", tile_height, + "max_tiles", 3 * tiles_across, + NULL ) ) + return( -1 ); + if( vips_image_write( t[1], load->real ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_load_jxl_class_init( VipsForeignLoadJxlClass *klass ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( klass ); + VipsObjectClass *object_class = (VipsObjectClass *) klass; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) klass; + + 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 JPEG2000 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; + + VIPS_ARG_INT( klass, "page", 20, + _( "Page" ), + _( "Load this page from the image" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadJxl, page ), + 0, 100000, 0 ); + +} + +static void +vips_foreign_load_jxl_init( VipsForeignLoadJxl *jxl ) +{ +} + +typedef struct _VipsForeignLoadJxlFile { + VipsForeignLoadJxl parent_object; + + /* Filename for load. + */ + char *filename; + +} VipsForeignLoadJxlFile; + +typedef VipsForeignLoadJxlClass VipsForeignLoadJxlFileClass; + +/* We need C linkage for this. + */ +extern "C" { +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[] = + { ".j2k", ".jp2", ".jpt", ".j2c", ".jpc", 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 *klass ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( klass ); + VipsObjectClass *object_class = (VipsObjectClass *) klass; + VipsForeignClass *foreign_class = (VipsForeignClass *) klass; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) klass; + + 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( klass, "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; + +/* We need C linkage for this. + */ +extern "C" { +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 *klass ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( klass ); + VipsObjectClass *object_class = (VipsObjectClass *) klass; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) klass; + + 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( klass, "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; + +/* We need C linkage for this. + */ +extern "C" { +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 *klass ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( klass ); + VipsObjectClass *object_class = (VipsObjectClass *) klass; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) klass; + + 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( klass, "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 + * + * Optional arguments: + * + * * @page: %gint, load this page + * + * Read a JPEG2000 image. The loader supports 8, 16 and 32-bit int pixel + * values, signed and unsigned. + * It supports greyscale, RGB, YCC, CMYK and + * multispectral colour spaces. + * It will read any ICC profile on + * the image. + * + * It will only load images where all channels are the same format. + * + * Use @page to set the page to load, where page 0 is the base resolution + * image and higher-numbered pages are x2 reductions. Use the metadata item + * "n-pages" to find the number of pyramid layers. + * + * 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 + * + * Optional arguments: + * + * * @page: %gint, load this page + * + * Exactly as vips_jxlload(), but read from a source. + * + * 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 + * + * Optional arguments: + * + * * @page: %gint, load this page + * + * 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/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 From 2a249a3049d97caaac5cfe49ef746baa0651c53c Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 1 Apr 2021 12:33:36 +0100 Subject: [PATCH 02/23] load header done --- configure.ac | 6 +- libvips/foreign/jxlload.cpp | 600 +++++---------------------- libvips/foreign/libnsgif/Makefile.am | 2 +- 3 files changed, 97 insertions(+), 511 deletions(-) diff --git a/configure.ac b/configure.ac index 28a631f1..7a53512a 100644 --- a/configure.ac +++ b/configure.ac @@ -1454,6 +1454,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 @@ -1464,7 +1467,6 @@ TIFF load/save with libtiff: $with_tiff image pyramid save: $with_gsf (requires libgsf-1 1.14.26 or later) HEIC/AVIF load/save with libheif: $with_heif -JXL load/save with libjxl: $with_libjxl WebP load/save with libwebp: $with_libwebp (requires libwebp, libwebpmux, libwebpdemux 0.6.0 or later) PDF load with PDFium: $with_pdfium @@ -1473,8 +1475,6 @@ PDF load with poppler-glib: $with_poppler SVG load with librsvg-2.0: $with_rsvg (requires librsvg-2.0 2.34.0 or later) EXR load with OpenEXR: $with_OpenEXR -JPEG2000 load/save with libopenjp2: $with_libopenjp2 - (requires libopenjp2 2.2 or later) slide load with OpenSlide: $with_openslide (requires openslide-3.3.0 or later) Matlab load with matio: $with_matio diff --git a/libvips/foreign/jxlload.cpp b/libvips/foreign/jxlload.cpp index 6da7683b..48d81ffe 100644 --- a/libvips/foreign/jxlload.cpp +++ b/libvips/foreign/jxlload.cpp @@ -75,6 +75,8 @@ typedef struct _VipsForeignLoadJxl { /* Base image properties. */ JxlBasicInfo info; + size_t icc_size; + uint8_t *icc_data; /* Decompress state. */ @@ -120,15 +122,25 @@ vips_foreign_load_jxl_dispose( GObject *gobject ) 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 *klass = VIPS_OBJECT_GET_CLASS( object ); + + /* TODO ... jxl has no way to get error messages at the moemnt. + */ + vips_error( klass->nickname, "%s", details ); +} + static int vips_foreign_load_jxl_build( VipsObject *object ) { - VipsObjectClass *klass = VIPS_OBJECT_GET_CLASS( object ); VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) object; JxlDecoderStatus status; @@ -141,18 +153,18 @@ vips_foreign_load_jxl_build( VipsObject *object ) vips_concurrency_get() ); jxl->decoder = JxlDecoderCreate( nullptr ); + /* We are only interested in end of header and end of pixel data. + */ if( JxlDecoderSubscribeEvents( jxl->decoder, - JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE ) != JXL_DEC_SUCCESS ) { - vips_error( klass->nickname, - "%s", _( "JxlDecoderSubscribeEvents failed" ) ); + vips_foreign_load_jxl_error( jxl, "JxlDecoderSubscribeEvents" ); return( -1 ); } if( JxlDecoderSetParallelRunner( jxl->decoder, JxlThreadParallelRunner, jxl->runner ) != JXL_DEC_SUCCESS ) { - vips_error( klass->nickname, - "%s", _( "JxlDecoderSetParallelRunner failed" ) ); + vips_foreign_load_jxl_error( jxl, + "JxlDecoderSetParallelRunner" ); return( -1 ); } @@ -163,115 +175,31 @@ vips_foreign_load_jxl_build( VipsObject *object ) return( 0 ); } +/* Always read as scRGBA. + */ +static JxlPixelFormat vips_foreign_load_jxl_format = + { 4, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0 }; + static int vips_foreign_load_jxl_set_header( VipsForeignLoadJxl *jxl, VipsImage *out ) { - VipsObjectClass *klass = VIPS_OBJECT_GET_CLASS( jxl ); - opj_image_comp_t *first = &jxl->image->comps[0]; - - VipsBandFormat format; - VipsInterpretation interpretation; - - /* OpenJPEG only supports up to 31 bpp. Treat it as 32. - */ - if( first->prec <= 8 ) - format = first->sgnd ? VIPS_FORMAT_CHAR : VIPS_FORMAT_UCHAR; - else if( first->prec <= 16 ) - format = first->sgnd ? VIPS_FORMAT_SHORT : VIPS_FORMAT_USHORT; - else - format = first->sgnd ? VIPS_FORMAT_INT : VIPS_FORMAT_UINT; - - switch( jxl->image->color_space ) { - case OPJ_CLRSPC_SYCC: - case OPJ_CLRSPC_EYCC: - /* Map these to RGB. - */ - interpretation = vips_format_sizeof( format ) == 1 ? - VIPS_INTERPRETATION_sRGB : - VIPS_INTERPRETATION_RGB16; - jxl->ycc_to_rgb = TRUE; - break; - - case OPJ_CLRSPC_GRAY: - interpretation = vips_format_sizeof( format ) == 1 ? - VIPS_INTERPRETATION_B_W : - VIPS_INTERPRETATION_GREY16; - break; - - case OPJ_CLRSPC_SRGB: - interpretation = vips_format_sizeof( format ) == 1 ? - VIPS_INTERPRETATION_sRGB : - VIPS_INTERPRETATION_RGB16; - break; - - case OPJ_CLRSPC_CMYK: - interpretation = VIPS_INTERPRETATION_CMYK; - break; - - case OPJ_CLRSPC_UNSPECIFIED: - /* Try to guess something sensible. - */ - if( jxl->image->numcomps < 3 ) - interpretation = vips_format_sizeof( format ) == 1 ? - VIPS_INTERPRETATION_B_W : - VIPS_INTERPRETATION_GREY16; - else - interpretation = vips_format_sizeof( format ) == 1 ? - VIPS_INTERPRETATION_sRGB : - VIPS_INTERPRETATION_RGB16; - - /* Unspecified with three bands and subsampling on bands 2 and - * 3 is usually YCC. - */ - if( jxl->image->numcomps == 3 && - jxl->image->comps[0].dx == 1 && - jxl->image->comps[0].dy == 1 && - jxl->image->comps[1].dx > 1 && - jxl->image->comps[1].dy > 1 && - jxl->image->comps[2].dx > 1 && - jxl->image->comps[2].dy > 1) - jxl->ycc_to_rgb = TRUE; - - break; - - default: - vips_error( klass->nickname, - _( "unsupported colourspace %d" ), - jxl->image->color_space ); - return( -1 ); - } - - /* Even though this is a tiled reader, we hint thinstrip since with - * the cache we are quite happy serving that if anything downstream + /* 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, - first->w, first->h, jxl->image->numcomps, format, - VIPS_CODING_NONE, interpretation, 1.0, 1.0 ); + jxl->info.width, jxl->info.height, 4, VIPS_FORMAT_FLOAT, + VIPS_CODING_NONE, VIPS_INTERPRETATION_scRGB, 1.0, 1.0 ); - /* openjpeg allows left and top of the coordinate grid to be - * non-zero. These are always in unshrunk coordinates. - */ - out->Xoffset = - -VIPS_ROUND_INT( (double) jxl->image->x0 / jxl->shrink ); - out->Yoffset = - -VIPS_ROUND_INT( (double) jxl->image->y0 / jxl->shrink ); - - if( jxl->image->icc_profile_buf && - jxl->image->icc_profile_len > 0 ) - vips_image_set_blob_copy( out, VIPS_META_ICC_NAME, - jxl->image->icc_profile_buf, - jxl->image->icc_profile_len ); - - /* Map number of layers in image to pages. - */ - if( jxl->info && - jxl->info->m_default_tile_info.tccp_info ) - vips_image_set_int( out, VIPS_META_N_PAGES, - jxl->info->m_default_tile_info.tccp_info-> - numresolutions ); + if( jxl->icc_data && + jxl->icc_size > 0 ) { + vips_image_set_blob( out, VIPS_META_ICC_NAME, vips_free, + jxl->icc_data, jxl->icc_size ); + jxl->icc_data = nullptr; + jxl->icc_size = 0; + } return( 0 ); } @@ -371,10 +299,11 @@ vips_foreign_load_jxl_process( VipsForeignLoadJxl *jxl ) while( (status = JxlDecoderProcessInput( jx->decoder )) == JXL_DEC_NEED_MORE_INPUT ) { - size_t bytes_remaining; + size_t unused; - bytes_remaining = JxlDecoderReleaseInput( jxl->decoder ); - if( vips_foreign_load_jxl_fill_input( jxl, bytes_remaining ) ) + unused = JxlDecoderReleaseInput( jxl->decoder ); + if( vips_foreign_load_jxl_fill_input( jxl, unused ) && + unused == 0 ) return( JXL_DEC_ERROR ); JxlDecoderSetInput( jxl->decoder, jxl->input_buffer, jxl->bytes_remaining ); @@ -386,17 +315,28 @@ vips_foreign_load_jxl_process( VipsForeignLoadJxl *jxl ) return( status ); } - static int vips_foreign_load_jxl_header( VipsForeignLoad *load ) { VipsObjectClass *klass = VIPS_OBJECT_GET_CLASS( load ); VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) load; + JxlDecoderStatus status; + #ifdef DEBUG printf( "vips_foreign_load_jxl_header:\n" ); #endif /*DEBUG*/ + /* 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.width, first->h, jxl->image->numcomps, format, + VIPS_CODING_NONE, interpretation, 1.0, 1.0 ); + if( vips_foreign_load_jxl_fill_input( jxl, 0 ) ) return( -1 ); JxlDecoderSetInput( jx->decoder, @@ -405,89 +345,48 @@ vips_foreign_load_jxl_header( VipsForeignLoad *load ) /* Read to the end of the header. */ do { - JxlDecoderStatus status; - - status = vips_foreign_load_jxl_process( jxl ); - if( status == JXL_DEC_ERROR ) + 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 ); + } + break; + + case JXL_DEC_COLOR_ENCODING: + if( JxlDecoderGetICCProfileSize( jxl->decoder, + &vips_foreign_load_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( nullptr, + jxl->icc_size )) ) + return( -1 ); + if( JxlDecoderGetColorAsICCProfile( + jxl->decoder, + 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_COLOR_ENCODING ); - JxlDecoderGetBasicInfo( jxl->decoder, &jxl->info ); - - - jxl->format = vips_foreign_load_jxl_get_format( jxl->source ); - vips_source_rewind( jxl->source ); - if( !(jxl->codec = opj_create_decompress( jxl->format )) ) - return( -1 ); - - vips_foreign_load_jxl_attach_handlers( jxl, jxl->codec ); - - jxl->shrink = 1 << jxl->page; - jxl->parameters.cp_reduce = jxl->page; - if( !opj_setup_decoder( jxl->codec, &jxl->parameters ) ) - return( -1 ); - -#ifdef HAVE_LIBJXL_THREADING - /* Use eg. VIPS_CONCURRENCY etc. to set n-cpus, if this openjpeg has - * stable support. - */ - opj_codec_set_threads( jxl->codec, vips_concurrency_get() ); -#endif /*HAVE_LIBJXL_THREADING*/ - - if( !opj_read_header( jxl->stream, jxl->codec, &jxl->image ) ) - return( -1 ); - if( !(jxl->info = opj_get_cstr_info( jxl->codec )) ) - return( -1 ); - -#ifdef DEBUG - vips_foreign_load_jxl_print( jxl ); -#endif /*DEBUG*/ - - /* We only allow images where all components have the same format. - */ - if( jxl->image->numcomps > MAX_BANDS ) { - vips_error( klass->nickname, - "%s", _( "too many image bands" ) ); - return( -1 ); - } - if( jxl->image->numcomps == 0 ) { - vips_error( klass->nickname, - "%s", _( "no image components" ) ); - return( -1 ); - } - first = &jxl->image->comps[0]; - for( i = 1; i < jxl->image->numcomps; i++ ) { - opj_image_comp_t *this = &jxl->image->comps[i]; - - if( this->x0 != first->x0 || - this->y0 != first->y0 || - this->w * this->dx != first->w * first->dx || - this->h * this->dy != first->h * first->dy || - this->resno_decoded != first->resno_decoded || - this->factor != first->factor ) { - vips_error( klass->nickname, - "%s", _( "components differ in geometry" ) ); - return( -1 ); - } - - if( this->prec != first->prec || - this->bpp != first->bpp || - this->sgnd != first->sgnd ) { - vips_error( klass->nickname, - "%s", _( "components differ in precision" ) ); - return( -1 ); - } - - /* If dx/dy are not 1, we'll need to upsample components during - * tile packing. - */ - if( this->dx != first->dx || - this->dy != first->dy || - first->dx != 1 || - first->dy != 1 ) - jxl->upsample = TRUE; - } - if( vips_foreign_load_jxl_set_header( jxl, load->out ) ) return( -1 ); @@ -497,281 +396,11 @@ vips_foreign_load_jxl_header( VipsForeignLoad *load ) return( 0 ); } -#define PACK( TYPE ) { \ - TYPE *tq = (TYPE *) q; \ - \ - for( x = 0; x < length; x++ ) { \ - for( i = 0; i < b; i++ ) \ - tq[i] = planes[i][x]; \ - \ - tq += b; \ - } \ -} - -#define PACK_UPSAMPLE( TYPE ) { \ - TYPE *tq = (TYPE *) q; \ - \ - for( x = 0; x < length; x++ ) { \ - for( i = 0; i < b; i++ ) { \ - int dx = jxl->image->comps[i].dx; \ - int pixel = planes[i][x / dx]; \ - \ - tq[i] = pixel; \ - } \ - \ - tq += b; \ - } \ -} - -/* Pack the set of openjpeg components into a libvips region. left/top are the - * offsets into the tile in pixel coordinates where we should start reading. - */ -static void -vips_foreign_load_jxl_pack( VipsForeignLoadJxl *jxl, - VipsImage *image, VipsPel *q, - int left, int top, int length ) -{ - int *planes[MAX_BANDS]; - int b = jxl->image->numcomps; - - int x, i; - - for( i = 0; i < b; i++ ) { - opj_image_comp_t *comp = &jxl->image->comps[i]; - - planes[i] = comp->data + (top / comp->dy) * comp->w + - (left / comp->dx); - } - - if( jxl->upsample ) - switch( image->BandFmt ) { - case VIPS_FORMAT_CHAR: - case VIPS_FORMAT_UCHAR: - PACK_UPSAMPLE( unsigned char ); - break; - - case VIPS_FORMAT_SHORT: - case VIPS_FORMAT_USHORT: - PACK_UPSAMPLE( unsigned short ); - break; - - case VIPS_FORMAT_INT: - case VIPS_FORMAT_UINT: - PACK_UPSAMPLE( unsigned int ); - break; - - default: - g_assert_not_reached(); - break; - } - else - /* Fast no-upsample path. - */ - switch( image->BandFmt ) { - case VIPS_FORMAT_CHAR: - case VIPS_FORMAT_UCHAR: - PACK( unsigned char ); - break; - - case VIPS_FORMAT_SHORT: - case VIPS_FORMAT_USHORT: - PACK( unsigned short ); - break; - - case VIPS_FORMAT_INT: - case VIPS_FORMAT_UINT: - PACK( unsigned int ); - break; - - default: - g_assert_not_reached(); - break; - } -} - -/* ycc->rgb coversion adapted from openjpeg src/bin/common/color.c - * - * See also https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion - */ -#define YCC_TO_RGB( TYPE ) { \ - TYPE *tq = (TYPE *) q; \ - \ - for( x = 0; x < length; x++ ) { \ - int y = tq[0]; \ - int cb = tq[1] - offset; \ - int cr = tq[2] - offset; \ - \ - int r, g, b; \ - \ - r = y + (int)(1.402 * (float)cr); \ - tq[0] = VIPS_CLIP( 0, r, upb ); \ - \ - g = y - (int)(0.344 * (float)cb + 0.714 * (float)cr); \ - tq[1] = VIPS_CLIP( 0, g, upb ); \ - \ - b = y + (int)(1.772 * (float)cb); \ - tq[2] = VIPS_CLIP( 0, b, upb ); \ - \ - tq += 3; \ - } \ -} - -/* YCC->RGB for a line of pels. - */ -static void -vips_foreign_load_jxl_ycc_to_rgb( VipsForeignLoadJxl *jxl, - VipsPel *q, int length ) -{ - VipsForeignLoad *load = (VipsForeignLoad *) jxl; - int prec = jxl->image->comps[0].prec; - int offset = 1 << (prec - 1); - int upb = (1 << prec) - 1; - - int x; - - switch( load->out->BandFmt ) { - case VIPS_FORMAT_CHAR: - case VIPS_FORMAT_UCHAR: - YCC_TO_RGB( unsigned char ); - break; - - case VIPS_FORMAT_SHORT: - case VIPS_FORMAT_USHORT: - YCC_TO_RGB( unsigned short ); - break; - - case VIPS_FORMAT_INT: - case VIPS_FORMAT_UINT: - YCC_TO_RGB( unsigned int ); - break; - - default: - g_assert_not_reached(); - break; - } -} - -/* Loop over the output region, painting in tiles from the file. - */ -static int -vips_foreign_load_jxl_generate( VipsRegion *out, - void *seq, void *a, void *b, gboolean *stop ) -{ - VipsForeignLoad *load = (VipsForeignLoad *) a; - VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) load; - VipsRect *r = &out->valid; - - /* jxl get smaller with the layer size. - */ - int tile_width = VIPS_ROUND_UINT( - (double) jxl->info->tdx / jxl->shrink ); - int tile_height = VIPS_ROUND_UINT( - (double) jxl->info->tdy / jxl->shrink ); - - /* ... so tiles_across is always the same. - */ - int tiles_across = jxl->info->tw; - - int x, y, z; - -#ifdef DEBUG_VERBOSE - printf( "vips_foreign_load_jxl_generate: " - "left = %d, top = %d, width = %d, height = %d\n", - r->left, r->top, r->width, r->height ); -#endif /*DEBUG_VERBOSE*/ - - /* If openjpeg has flagged an error, the library is not in a known - * state and it's not safe to call again. - */ - if( jxl->n_errors ) - return( 0 ); - - y = 0; - while( y < r->height ) { - VipsRect tile, hit; - - /* Not necessary, but it stops static analyzers complaining - * about a used-before-set. - */ - hit.height = 0; - - x = 0; - while( x < r->width ) { - /* Tile the xy falls in, in tile numbers. - */ - int tx = (r->left + x) / tile_width; - int ty = (r->top + y) / tile_height; - - /* Pixel coordinates of the tile that xy falls in. - */ - int xs = tx * tile_width; - int ys = ty * tile_height; - - int tile_index = ty * tiles_across + tx; - - /* Fetch the tile. - */ -#ifdef DEBUG_VERBOSE - printf( " fetch tile %d\n", tile_index ); -#endif /*DEBUG_VERBOSE*/ - if( !opj_get_decoded_tile( jxl->codec, - jxl->stream, jxl->image, tile_index ) ) - return( -1 ); - - /* Intersect tile with request to get pixels we need - * to copy out. - */ - tile.left = xs; - tile.top = ys; - tile.width = tile_width; - tile.height = tile_height; - vips_rect_intersectrect( &tile, r, &hit ); - - /* Unpack hit pixels to buffer in vips layout. - */ - for( z = 0; z < hit.height; z++ ) { - VipsPel *q = VIPS_REGION_ADDR( out, - hit.left, hit.top + z ); - - vips_foreign_load_jxl_pack( jxl, - out->im, q, - hit.left - tile.left, - hit.top - tile.top + z, - hit.width ); - - if( jxl->ycc_to_rgb ) - vips_foreign_load_jxl_ycc_to_rgb( jxl, - q, hit.width ); - } - - x += hit.width; - } - - /* This will be the same for all tiles in the row we've just - * done. - */ - y += hit.height; - } - - if( load->fail && - jxl->n_errors > 0 ) - return( -1 ); - - return( 0 ); -} - static int vips_foreign_load_jxl_load( VipsForeignLoad *load ) { VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) load; - /* jxl tiles get smaller with the layer size, but we don't want tiny - * tiles for the libvips tile cache, so leave them at the base size. - */ - int tile_width = jxl->info->tdx; - int tile_height = jxl->info->tdy; - int tiles_across = jxl->info->tw; - VipsImage **t = (VipsImage **) vips_object_local_array( VIPS_OBJECT( load ), 3 ); @@ -783,20 +412,7 @@ vips_foreign_load_jxl_load( VipsForeignLoad *load ) if( vips_foreign_load_jxl_set_header( jxl, t[0] ) ) return( -1 ); - if( vips_image_generate( t[0], - NULL, vips_foreign_load_jxl_generate, NULL, jxl, NULL ) ) - return( -1 ); - - /* Copy to out, adding a cache. Enough tiles for two complete - * rows, plus 50%. - */ - if( vips_tilecache( t[0], &t[1], - "tile_width", tile_width, - "tile_height", tile_height, - "max_tiles", 3 * tiles_across, - NULL ) ) - return( -1 ); - if( vips_image_write( t[1], load->real ) ) + if( vips_image_write( t[0], load->real ) ) return( -1 ); return( 0 ); @@ -814,20 +430,13 @@ vips_foreign_load_jxl_class_init( VipsForeignLoadJxlClass *klass ) gobject_class->get_property = vips_object_get_property; object_class->nickname = "jxlload_base"; - object_class->description = _( "load JPEG2000 image" ); + 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; - VIPS_ARG_INT( klass, "page", 20, - _( "Page" ), - _( "Load this page from the image" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadJxl, page ), - 0, 100000, 0 ); - } static void @@ -871,7 +480,7 @@ vips_foreign_load_jxl_file_build( VipsObject *object ) } const char *vips__jxl_suffs[] = - { ".j2k", ".jp2", ".jpt", ".j2c", ".jpc", NULL }; + { ".jxl", NULL }; static int vips_foreign_load_jxl_is_a( const char *filename ) @@ -1079,22 +688,7 @@ vips_foreign_load_jxl_source_init( * @out: (out): decompressed image * @...: %NULL-terminated list of optional named arguments * - * Optional arguments: - * - * * @page: %gint, load this page - * - * Read a JPEG2000 image. The loader supports 8, 16 and 32-bit int pixel - * values, signed and unsigned. - * It supports greyscale, RGB, YCC, CMYK and - * multispectral colour spaces. - * It will read any ICC profile on - * the image. - * - * It will only load images where all channels are the same format. - * - * Use @page to set the page to load, where page 0 is the base resolution - * image and higher-numbered pages are x2 reductions. Use the metadata item - * "n-pages" to find the number of pyramid layers. + * Read a JPEG-XL image. * * See also: vips_image_new_from_file(). * @@ -1120,10 +714,6 @@ vips_jxlload( const char *filename, VipsImage **out, ... ) * @out: (out): image to write * @...: %NULL-terminated list of optional named arguments * - * Optional arguments: - * - * * @page: %gint, load this page - * * Exactly as vips_jxlload(), but read from a source. * * Returns: 0 on success, -1 on error. @@ -1154,10 +744,6 @@ vips_jxlload_buffer( void *buf, size_t len, VipsImage **out, ... ) * @out: (out): decompressed image * @...: %NULL-terminated list of optional named arguments * - * Optional arguments: - * - * * @page: %gint, load this page - * * Exactly as vips_jxlload(), but read from a source. * * Returns: 0 on success, -1 on error. diff --git a/libvips/foreign/libnsgif/Makefile.am b/libvips/foreign/libnsgif/Makefile.am index 0ab13f5b..26a31fb6 100644 --- a/libvips/foreign/libnsgif/Makefile.am +++ b/libvips/foreign/libnsgif/Makefile.am @@ -2,7 +2,7 @@ EXTRA_DIST = \ README-ns \ README.md \ patches \ - update.sh \ + update.sh \ utils noinst_LTLIBRARIES = libnsgif.la From 950606406fbd3649aa1627991535225519c83a5c Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 1 Apr 2021 16:54:42 +0100 Subject: [PATCH 03/23] compiles --- configure.ac | 2 +- libvips/foreign/Makefile.am | 2 +- libvips/foreign/foreign.c | 3 +- libvips/foreign/{jxlload.cpp => jxlload.c} | 78 +++++++++------------- libvips/foreign/ppmsave.c | 1 + 5 files changed, 38 insertions(+), 48 deletions(-) rename libvips/foreign/{jxlload.cpp => jxlload.c} (92%) diff --git a/configure.ac b/configure.ac index 7a53512a..a25a24fc 100644 --- a/configure.ac +++ b/configure.ac @@ -776,7 +776,7 @@ AC_ARG_WITH([libjxl], [build without libjxl (default: test)])) if test x"$with_libjxl" != x"no"; then - PKG_CHECK_MODULES(LIBJXL, libjxl >= 0.3.7, + PKG_CHECK_MODULES(LIBJXL, libjxl_threads >= 0.3.7 libjxl >= 0.3.7, [AC_DEFINE(HAVE_LIBJXL,1, [define if you have libjxl >=2.2 installed.]) with_libjxl=yes diff --git a/libvips/foreign/Makefile.am b/libvips/foreign/Makefile.am index a51a056b..90679c40 100644 --- a/libvips/foreign/Makefile.am +++ b/libvips/foreign/Makefile.am @@ -19,7 +19,7 @@ libforeign_la_SOURCES = \ foreign.c \ heifload.c \ heifsave.c \ - jxlload.cpp \ + jxlload.c \ jp2kload.c \ jp2ksave.c \ jpeg2vips.c \ diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index d1cede50..c94a3546 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 || diff --git a/libvips/foreign/jxlload.cpp b/libvips/foreign/jxlload.c similarity index 92% rename from libvips/foreign/jxlload.cpp rename to libvips/foreign/jxlload.c index 48d81ffe..c66393cb 100644 --- a/libvips/foreign/jxlload.cpp +++ b/libvips/foreign/jxlload.c @@ -52,9 +52,7 @@ #ifdef HAVE_LIBJXL #include -#include #include -#include #include "pforeign.h" @@ -80,7 +78,7 @@ typedef struct _VipsForeignLoadJxl { /* Decompress state. */ - JxlThreadParallelRunner *runner; + void *runner; JxlDecoder *decoder; /* Our input buffer. @@ -104,12 +102,8 @@ typedef struct _VipsForeignLoadJxl { typedef VipsForeignLoadClass VipsForeignLoadJxlClass; -/* We need C linkage for this. - */ -extern "C" { G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadJxl, vips_foreign_load_jxl, VIPS_TYPE_FOREIGN_LOAD ); -} static void vips_foreign_load_jxl_dispose( GObject *gobject ) @@ -131,7 +125,7 @@ vips_foreign_load_jxl_dispose( GObject *gobject ) static void vips_foreign_load_jxl_error( VipsForeignLoadJxl *jxl, const char *details ) { - VipsObjectClass *klass = VIPS_OBJECT_GET_CLASS( object ); + VipsObjectClass *klass = VIPS_OBJECT_GET_CLASS( jxl ); /* TODO ... jxl has no way to get error messages at the moemnt. */ @@ -143,15 +137,13 @@ vips_foreign_load_jxl_build( VipsObject *object ) { VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) object; - JxlDecoderStatus status; - #ifdef DEBUG printf( "vips_foreign_load_jxl_build:\n" ); #endif /*DEBUG*/ - jxl->runner = JxlThreadParallelRunnerCreate( nullptr, + jxl->runner = JxlThreadParallelRunnerCreate( NULL, vips_concurrency_get() ); - jxl->decoder = JxlDecoderCreate( nullptr ); + jxl->decoder = JxlDecoderCreate( NULL ); /* We are only interested in end of header and end of pixel data. */ @@ -175,6 +167,22 @@ vips_foreign_load_jxl_build( VipsObject *object ) 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_CONTAINER ); +} + +static VipsForeignFlags +vips_foreign_load_jxl_get_flags( VipsForeignLoad *load ) +{ + return( VIPS_FOREIGN_PARTIAL ); +} + /* Always read as scRGBA. */ static JxlPixelFormat vips_foreign_load_jxl_format = @@ -190,14 +198,15 @@ vips_foreign_load_jxl_set_header( VipsForeignLoadJxl *jxl, VipsImage *out ) vips_image_pipelinev( out, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); vips_image_init_fields( out, - jxl->info.width, jxl->info.height, 4, VIPS_FORMAT_FLOAT, + jxl->info.xsize, jxl->info.ysize, 4, VIPS_FORMAT_FLOAT, VIPS_CODING_NONE, VIPS_INTERPRETATION_scRGB, 1.0, 1.0 ); if( jxl->icc_data && jxl->icc_size > 0 ) { - vips_image_set_blob( out, VIPS_META_ICC_NAME, vips_free, + vips_image_set_blob( out, VIPS_META_ICC_NAME, + (VipsCallbackFn) vips_area_free_cb, jxl->icc_data, jxl->icc_size ); - jxl->icc_data = nullptr; + jxl->icc_data = NULL; jxl->icc_size = 0; } @@ -297,7 +306,7 @@ vips_foreign_load_jxl_process( VipsForeignLoadJxl *jxl ) { JxlDecoderStatus status; - while( (status = JxlDecoderProcessInput( jx->decoder )) == + while( (status = JxlDecoderProcessInput( jxl->decoder )) == JXL_DEC_NEED_MORE_INPUT ) { size_t unused; @@ -306,7 +315,7 @@ vips_foreign_load_jxl_process( VipsForeignLoadJxl *jxl ) unused == 0 ) return( JXL_DEC_ERROR ); JxlDecoderSetInput( jxl->decoder, - jxl->input_buffer, jxl->bytes_remaining ); + jxl->input_buffer, jxl->bytes_in_buffer ); } printf( "vips_foreign_load_jxl_process: seen " ); @@ -318,7 +327,6 @@ vips_foreign_load_jxl_process( VipsForeignLoadJxl *jxl ) static int vips_foreign_load_jxl_header( VipsForeignLoad *load ) { - VipsObjectClass *klass = VIPS_OBJECT_GET_CLASS( load ); VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) load; JxlDecoderStatus status; @@ -327,20 +335,10 @@ vips_foreign_load_jxl_header( VipsForeignLoad *load ) printf( "vips_foreign_load_jxl_header:\n" ); #endif /*DEBUG*/ - /* 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.width, first->h, jxl->image->numcomps, format, - VIPS_CODING_NONE, interpretation, 1.0, 1.0 ); - if( vips_foreign_load_jxl_fill_input( jxl, 0 ) ) return( -1 ); - JxlDecoderSetInput( jx->decoder, - jxl->input_buffer, jxl->bytes_remaining ); + JxlDecoderSetInput( jxl->decoder, + jxl->input_buffer, jxl->bytes_in_buffer ); /* Read to the end of the header. */ @@ -369,11 +367,13 @@ vips_foreign_load_jxl_header( VipsForeignLoad *load ) "JxlDecoderGetICCProfileSize" ); return( -1 ); } - if( !(jxl->icc_data = vips_malloc( nullptr, + if( !(jxl->icc_data = vips_malloc( NULL, jxl->icc_size )) ) return( -1 ); - if( JxlDecoderGetColorAsICCProfile( - jxl->decoder, + + + if( JxlDecoderGetColorAsICCProfile( jxl->decoder, + &vips_foreign_load_jxl_format, JXL_COLOR_PROFILE_TARGET_DATA, jxl->icc_data, jxl->icc_size ) ) { vips_foreign_load_jxl_error( jxl, @@ -455,12 +455,8 @@ typedef struct _VipsForeignLoadJxlFile { typedef VipsForeignLoadJxlClass VipsForeignLoadJxlFileClass; -/* We need C linkage for this. - */ -extern "C" { 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 ) @@ -540,12 +536,8 @@ typedef struct _VipsForeignLoadJxlBuffer { typedef VipsForeignLoadJxlClass VipsForeignLoadJxlBufferClass; -/* We need C linkage for this. - */ -extern "C" { 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 ) @@ -622,12 +614,8 @@ typedef struct _VipsForeignLoadJxlSource { typedef VipsForeignLoadJxlClass VipsForeignLoadJxlSourceClass; -/* We need C linkage for this. - */ -extern "C" { 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 ) 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; From 8734eb3c8ab7f68ad1c43a36e3ffdd0b689f2b66 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 1 Apr 2021 17:06:51 +0100 Subject: [PATCH 04/23] header works --- libvips/foreign/jxlload.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/libvips/foreign/jxlload.c b/libvips/foreign/jxlload.c index c66393cb..4e884346 100644 --- a/libvips/foreign/jxlload.c +++ b/libvips/foreign/jxlload.c @@ -145,10 +145,9 @@ vips_foreign_load_jxl_build( VipsObject *object ) vips_concurrency_get() ); jxl->decoder = JxlDecoderCreate( NULL ); - /* We are only interested in end of header and end of pixel data. - */ if( JxlDecoderSubscribeEvents( jxl->decoder, JXL_DEC_COLOR_ENCODING | + JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE ) != JXL_DEC_SUCCESS ) { vips_foreign_load_jxl_error( jxl, "JxlDecoderSubscribeEvents" ); return( -1 ); @@ -174,7 +173,7 @@ vips_foreign_load_jxl_is_a_source( VipsSource *source ) JxlSignature sig; return( (p = vips_source_sniff( source, 12 )) && - (sig = JxlSignatureCheck( p, 12 )) == JXL_SIG_CONTAINER ); + (sig = JxlSignatureCheck( p, 12 )) == JXL_SIG_CODESTREAM ); } static VipsForeignFlags @@ -385,7 +384,7 @@ vips_foreign_load_jxl_header( VipsForeignLoad *load ) default: break; } - } while( status != JXL_DEC_COLOR_ENCODING ); + } while( status != JXL_DEC_NEED_IMAGE_OUT_BUFFER ); if( vips_foreign_load_jxl_set_header( jxl, load->out ) ) return( -1 ); From 9b8f308d9eccb4e8e5704f93b0a8946e85a50c4d Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 2 Apr 2021 18:35:35 +0100 Subject: [PATCH 05/23] jxl load sort-of works --- libvips/foreign/jxlload.c | 185 +++++++++++++++++++++++++++----------- 1 file changed, 134 insertions(+), 51 deletions(-) diff --git a/libvips/foreign/jxlload.c b/libvips/foreign/jxlload.c index 4e884346..95186c30 100644 --- a/libvips/foreign/jxlload.c +++ b/libvips/foreign/jxlload.c @@ -125,11 +125,11 @@ vips_foreign_load_jxl_dispose( GObject *gobject ) static void vips_foreign_load_jxl_error( VipsForeignLoadJxl *jxl, const char *details ) { - VipsObjectClass *klass = VIPS_OBJECT_GET_CLASS( jxl ); + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( jxl ); /* TODO ... jxl has no way to get error messages at the moemnt. */ - vips_error( klass->nickname, "%s", details ); + vips_error( class->nickname, "%s", details ); } static int @@ -187,31 +187,6 @@ vips_foreign_load_jxl_get_flags( VipsForeignLoad *load ) static JxlPixelFormat vips_foreign_load_jxl_format = { 4, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0 }; -static int -vips_foreign_load_jxl_set_header( VipsForeignLoadJxl *jxl, VipsImage *out ) -{ - /* 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, 4, VIPS_FORMAT_FLOAT, - VIPS_CODING_NONE, VIPS_INTERPRETATION_scRGB, 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; - } - - return( 0 ); -} - static int vips_foreign_load_jxl_fill_input( VipsForeignLoadJxl *jxl, size_t bytes_remaining ) @@ -323,6 +298,67 @@ vips_foreign_load_jxl_process( VipsForeignLoadJxl *jxl ) return( status ); } +static void +vips_foreign_load_jxl_print_info( VipsForeignLoadJxl *jxl ) +{ + printf( "vips_foreign_load_jxl_print_info:\n" ); + printf( " have_container = %d\n", jxl->info.have_container ); + printf( " xsize = %d\n", jxl->info.xsize ); + printf( " ysize = %d\n", jxl->info.ysize ); + printf( " bits_per_sample = %d\n", jxl->info.bits_per_sample ); + printf( " exponent_bits_per_sample = %d\n", + jxl->info.exponent_bits_per_sample ); + printf( " intensity_target = %g\n", jxl->info.intensity_target ); + printf( " min_nits = %g\n", jxl->info.min_nits ); + printf( " relative_to_max_display = %d\n", + jxl->info.relative_to_max_display ); + printf( " linear_below = %g\n", jxl->info.linear_below ); + printf( " uses_original_profile = %d\n", + jxl->info.uses_original_profile ); + printf( " have_preview = %d\n", jxl->info.have_preview ); + printf( " have_animation = %d\n", jxl->info.have_animation ); + printf( " orientation = %d\n", jxl->info.orientation ); + printf( " num_color_channels = %d\n", jxl->info.num_color_channels ); + printf( " num_extra_channels = %d\n", jxl->info.num_extra_channels ); + printf( " alpha_bits = %d\n", jxl->info.alpha_bits ); + printf( " alpha_exponent_bits = %d\n", jxl->info.alpha_exponent_bits ); + printf( " alpha_premultiplied = %d\n", jxl->info.alpha_premultiplied ); + printf( " preview.xsize = %d\n", jxl->info.preview.xsize ); + printf( " preview.ysize = %d\n", jxl->info.preview.ysize ); + printf( " animation.tps_numerator = %d\n", + jxl->info.animation.tps_numerator ); + printf( " animation.tps_denominator = %d\n", + jxl->info.animation.tps_denominator ); + printf( " animation.num_loops = %d\n", jxl->info.animation.num_loops ); + printf( " animation.have_timecodes = %d\n", + jxl->info.animation.have_timecodes ); +} + +static int +vips_foreign_load_jxl_set_header( VipsForeignLoadJxl *jxl, VipsImage *out ) +{ + /* 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, 4, VIPS_FORMAT_FLOAT, + VIPS_CODING_NONE, VIPS_INTERPRETATION_scRGB, 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; + } + + return( 0 ); +} + static int vips_foreign_load_jxl_header( VipsForeignLoad *load ) { @@ -355,6 +391,7 @@ vips_foreign_load_jxl_header( VipsForeignLoad *load ) "JxlDecoderGetBasicInfo" ); return( -1 ); } + vips_foreign_load_jxl_print_info( jxl ); break; case JXL_DEC_COLOR_ENCODING: @@ -398,11 +435,14 @@ vips_foreign_load_jxl_header( VipsForeignLoad *load ) 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*/ @@ -411,6 +451,52 @@ vips_foreign_load_jxl_load( VipsForeignLoad *load ) 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, + &vips_foreign_load_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, + &vips_foreign_load_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 ); @@ -418,11 +504,11 @@ vips_foreign_load_jxl_load( VipsForeignLoad *load ) } static void -vips_foreign_load_jxl_class_init( VipsForeignLoadJxlClass *klass ) +vips_foreign_load_jxl_class_init( VipsForeignLoadJxlClass *class ) { - GObjectClass *gobject_class = G_OBJECT_CLASS( klass ); - VipsObjectClass *object_class = (VipsObjectClass *) klass; - VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) klass; + 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; @@ -492,13 +578,12 @@ vips_foreign_load_jxl_is_a( const char *filename ) } static void -vips_foreign_load_jxl_file_class_init( - VipsForeignLoadJxlFileClass *klass ) +vips_foreign_load_jxl_file_class_init( VipsForeignLoadJxlFileClass *class ) { - GObjectClass *gobject_class = G_OBJECT_CLASS( klass ); - VipsObjectClass *object_class = (VipsObjectClass *) klass; - VipsForeignClass *foreign_class = (VipsForeignClass *) klass; - VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) klass; + 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; @@ -510,7 +595,7 @@ vips_foreign_load_jxl_file_class_init( load_class->is_a = vips_foreign_load_jxl_is_a; - VIPS_ARG_STRING( klass, "filename", 1, + VIPS_ARG_STRING( class, "filename", 1, _( "Filename" ), _( "Filename to load from" ), VIPS_ARGUMENT_REQUIRED_INPUT, @@ -573,12 +658,11 @@ vips_foreign_load_jxl_buffer_is_a( const void *buf, size_t len ) } static void -vips_foreign_load_jxl_buffer_class_init( - VipsForeignLoadJxlBufferClass *klass ) +vips_foreign_load_jxl_buffer_class_init( VipsForeignLoadJxlBufferClass *class ) { - GObjectClass *gobject_class = G_OBJECT_CLASS( klass ); - VipsObjectClass *object_class = (VipsObjectClass *) klass; - VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) klass; + 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; @@ -588,7 +672,7 @@ vips_foreign_load_jxl_buffer_class_init( load_class->is_a_buffer = vips_foreign_load_jxl_buffer_is_a; - VIPS_ARG_BOXED( klass, "buffer", 1, + VIPS_ARG_BOXED( class, "buffer", 1, _( "Buffer" ), _( "Buffer to load from" ), VIPS_ARGUMENT_REQUIRED_INPUT, @@ -637,12 +721,11 @@ vips_foreign_load_jxl_source_build( VipsObject *object ) } static void -vips_foreign_load_jxl_source_class_init( - VipsForeignLoadJxlSourceClass *klass ) +vips_foreign_load_jxl_source_class_init( VipsForeignLoadJxlSourceClass *class ) { - GObjectClass *gobject_class = G_OBJECT_CLASS( klass ); - VipsObjectClass *object_class = (VipsObjectClass *) klass; - VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) klass; + 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; @@ -652,7 +735,7 @@ vips_foreign_load_jxl_source_class_init( load_class->is_a_source = vips_foreign_load_jxl_is_a_source; - VIPS_ARG_OBJECT( klass, "source", 1, + VIPS_ARG_OBJECT( class, "source", 1, _( "Source" ), _( "Source to load from" ), VIPS_ARGUMENT_REQUIRED_INPUT, From 10a0c5a4bb648b041c3d1a2f04bafad2cd825763 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 3 Apr 2021 11:57:11 +0100 Subject: [PATCH 06/23] start adding jxlsave --- libvips/foreign/Makefile.am | 1 + libvips/foreign/foreign.c | 6 + libvips/foreign/jp2ksave.c | 2 +- libvips/foreign/jxlsave.c | 624 ++++++++++++++++++++++++++++++++++++ libvips/foreign/pforeign.h | 2 + 5 files changed, 634 insertions(+), 1 deletion(-) create mode 100644 libvips/foreign/jxlsave.c diff --git a/libvips/foreign/Makefile.am b/libvips/foreign/Makefile.am index 90679c40..4602b607 100644 --- a/libvips/foreign/Makefile.am +++ b/libvips/foreign/Makefile.am @@ -20,6 +20,7 @@ libforeign_la_SOURCES = \ heifload.c \ heifsave.c \ jxlload.c \ + jxlsave.c \ jp2kload.c \ jp2ksave.c \ jpeg2vips.c \ diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index c94a3546..f039f7b4 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -2187,6 +2187,9 @@ vips_foreign_operation_init( 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 ); @@ -2265,6 +2268,9 @@ vips_foreign_operation_init( void ) 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_source_get_type(); #endif /*HAVE_LIBJXL*/ #ifdef HAVE_LIBOPENJP2 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/jxlsave.c b/libvips/foreign/jxlsave.c new file mode 100644 index 00000000..18e2c3f6 --- /dev/null +++ b/libvips/foreign/jxlsave.c @@ -0,0 +1,624 @@ +/* save as jpeg2000 + * + * 18/3/20 + * - from jxlload.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" + +typedef struct _VipsForeignSaveJp2k { + VipsForeignSave parent_object; + + /* Where to write (set by subclasses). + */ + VipsTarget *target; + + /* Base image properties. + */ + JxlBasicInfo info; + size_t icc_size; + uint8_t *icc_data; + + /* Encoder state. + */ + void *runner; + JxlEncoder *encoder; + +} VipsForeignSaveJp2k; + +typedef VipsForeignSaveClass VipsForeignSaveJp2kClass; + +G_DEFINE_ABSTRACT_TYPE( VipsForeignSaveJp2k, vips_foreign_save_jxl, + VIPS_TYPE_FOREIGN_SAVE ); + +static void +vips_foreign_save_jxl_dispose( GObject *gobject ) +{ + VipsForeignSaveJp2k *jxl = (VipsForeignSaveJp2k *) gobject; + + VIPS_FREEF( JxlThreadParallelRunnerDestroy, jxl->runner ); + VIPS_FREEF( JxlEncoderDestroy, jxl->encoder ); + + G_OBJECT_CLASS( vips_foreign_save_jxl_parent_class )-> + dispose( gobject ); +} + +static int +vips_foreign_save_jxl_build( VipsObject *object ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); + VipsForeignSave *save = (VipsForeignSave *) object; + VipsForeignSaveJp2k *jxl = (VipsForeignSaveJp2k *) object; + + OPJ_COLOR_SPACE color_space; + int expected_bands; + int bits_per_pixel; + int i; + size_t sizeof_tile; + size_t sizeof_line; + VipsRect strip_position; + + if( VIPS_OBJECT_CLASS( vips_foreign_save_jxl_parent_class )-> + build( object ) ) + return( -1 ); + + opj_set_default_encoder_parameters( &jxl->parameters ); + + /* Analyze our arguments. + */ + + if( !vips_band_format_isint( save->ready->BandFmt ) ) { + vips_error( class->nickname, + "%s", _( "not an integer format" ) ); + return( -1 ); + } + + switch( jxl->subsample_mode ) { + case VIPS_FOREIGN_SUBSAMPLE_AUTO: + jxl->downsample = + !jxl->lossless && + jxl->Q < 90 && + save->ready->Xsize % 2 == 0 && + save->ready->Ysize % 2 == 0 && + (save->ready->Type == VIPS_INTERPRETATION_sRGB || + save->ready->Type == VIPS_INTERPRETATION_RGB16) && + save->ready->Bands == 3; + break; + + case VIPS_FOREIGN_SUBSAMPLE_ON: + jxl->downsample = TRUE; + break; + + case VIPS_FOREIGN_SUBSAMPLE_OFF: + jxl->downsample = FALSE; + break; + + default: + g_assert_not_reached(); + break; + } + + if( jxl->downsample ) + jxl->save_as_ycc = TRUE; + + /* CIELAB etc. do not seem to be well documented. + */ + switch( save->ready->Type ) { + case VIPS_INTERPRETATION_B_W: + case VIPS_INTERPRETATION_GREY16: + color_space = OPJ_CLRSPC_GRAY; + expected_bands = 1; + break; + + case VIPS_INTERPRETATION_sRGB: + case VIPS_INTERPRETATION_RGB16: + color_space = jxl->save_as_ycc ? + OPJ_CLRSPC_SYCC : OPJ_CLRSPC_SRGB; + expected_bands = 3; + break; + + case VIPS_INTERPRETATION_CMYK: + color_space = OPJ_CLRSPC_CMYK; + expected_bands = 4; + break; + + default: + color_space = OPJ_CLRSPC_UNSPECIFIED; + expected_bands = save->ready->Bands; + break; + } + + switch( save->ready->BandFmt ) { + case VIPS_FORMAT_CHAR: + case VIPS_FORMAT_UCHAR: + bits_per_pixel = 8; + break; + + case VIPS_FORMAT_SHORT: + case VIPS_FORMAT_USHORT: + bits_per_pixel = 16; + break; + + case VIPS_FORMAT_INT: + case VIPS_FORMAT_UINT: + /* OpenJPEG only supports up to 31. + */ + bits_per_pixel = 31; + break; + + default: + g_assert_not_reached(); + break; + } + + /* Set parameters for compressor. + */ + + /* Always tile. + */ + jxl->parameters.tile_size_on = OPJ_TRUE; + jxl->parameters.cp_tdx = jxl->tile_width; + jxl->parameters.cp_tdy = jxl->tile_height; + + /* Number of layers to write. Smallest layer is c. 2^5 on the smallest + * axis. + */ + jxl->parameters.numresolution = VIPS_MAX( 1, + log( VIPS_MIN( save->ready->Xsize, save->ready->Ysize ) ) / + log( 2 ) - 4 ); +#ifdef DEBUG + printf( "vips_foreign_save_jxl_build: numresolutions = %d\n", + jxl->parameters.numresolution ); +#endif /*DEBUG*/ + + for( i = 0; i < save->ready->Bands; i++ ) { + jxl->comps[i].dx = (jxl->downsample && i > 0) ? 2 : 1; + jxl->comps[i].dy = (jxl->downsample && i > 0) ? 2 : 1; + jxl->comps[i].w = save->ready->Xsize; + jxl->comps[i].h = save->ready->Ysize; + jxl->comps[i].x0 = 0; + jxl->comps[i].y0 = 0; + jxl->comps[i].prec = bits_per_pixel; + jxl->comps[i].bpp = bits_per_pixel; + jxl->comps[i].sgnd = + !vips_band_format_isuint( save->ready->BandFmt ); + } + + /* Makes three band images smaller, somehow. + */ + jxl->parameters.tcp_mct = + (save->ready->Bands == 3 && !jxl->downsample) ? 1 : 0; + + /* Lossy mode. + */ + if( !jxl->lossless ) { + jxl->parameters.irreversible = TRUE; + + /* Map Q to allowed distortion. + */ + jxl->parameters.cp_disto_alloc = 1; + jxl->parameters.cp_fixed_quality = TRUE; + jxl->parameters.tcp_distoratio[0] = jxl->Q; + jxl->parameters.tcp_numlayers = 1; + } + + /* Create output image. + */ + + jxl->image = opj_image_create( save->ready->Bands, + jxl->comps, color_space ); + jxl->image->x1 = save->ready->Xsize; + jxl->image->y1 = save->ready->Ysize; + + /* Tag alpha channels. + */ + for( i = 0; i < save->ready->Bands; i++ ) + jxl->image->comps[i].alpha = i >= expected_bands; + + /* Set up compressor. + */ + + jxl->codec = opj_create_compress( OPJ_CODEC_J2K ); + vips_foreign_save_jxl_attach_handlers( jxl, jxl->codec ); + if( !opj_setup_encoder( jxl->codec, &jxl->parameters, jxl->image ) ) + return( -1 ); + +#ifdef HAVE_LIBOPENJP2_THREADING + /* Use eg. VIPS_CONCURRENCY etc. to set n-cpus, if this openjpeg has + * stable support. + */ + opj_codec_set_threads( jxl->codec, vips_concurrency_get() ); +#endif /*HAVE_LIBOPENJP2_THREADING*/ + + if( !(jxl->stream = vips_foreign_save_jxl_target( jxl->target )) ) + return( -1 ); + + if( !opj_start_compress( jxl->codec, jxl->image, jxl->stream ) ) + return( -1 ); + + /* The buffer we repack tiles to for write. Large enough for one + * complete tile. + */ + sizeof_tile = VIPS_IMAGE_SIZEOF_PEL( save->ready ) * + jxl->tile_width * jxl->tile_height; + if( !(jxl->tile_buffer = VIPS_ARRAY( NULL, sizeof_tile, VipsPel )) ) + return( -1 ); + + /* We need a line of sums for chroma subsample. At worst, gint64. + */ + sizeof_line = sizeof( gint64 ) * jxl->tile_width; + if( !(jxl->accumulate = VIPS_ARRAY( NULL, sizeof_line, VipsPel )) ) + return( -1 ); + + /* The line of tiles we are building. + */ + jxl->strip = vips_region_new( save->ready ); + + /* Position strip at the top of the image, the height of a row of + * tiles. + */ + strip_position.left = 0; + strip_position.top = 0; + strip_position.width = save->ready->Xsize; + strip_position.height = jxl->tile_height; + if( vips_region_buffer( jxl->strip, &strip_position ) ) + return( -1 ); + + /* Write data. + */ + if( vips_sink_disc( save->ready, + vips_foreign_save_jxl_write_block, jxl ) ) + return( -1 ); + + opj_end_compress( jxl->codec, jxl->stream ); + + vips_target_finish( jxl->target ); + + return( 0 ); +} + +static void +vips_foreign_save_jxl_class_init( VipsForeignSaveJp2kClass *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; + +} + +static void +vips_foreign_save_jxl_init( VipsForeignSaveJp2k *jxl ) +{ +} + +typedef struct _VipsForeignSaveJp2kFile { + VipsForeignSaveJp2k parent_object; + + /* Filename for save. + */ + char *filename; + +} VipsForeignSaveJp2kFile; + +typedef VipsForeignSaveJp2kClass VipsForeignSaveJp2kFileClass; + +G_DEFINE_TYPE( VipsForeignSaveJp2kFile, vips_foreign_save_jxl_file, + vips_foreign_save_jxl_get_type() ); + +static int +vips_foreign_save_jxl_file_build( VipsObject *object ) +{ + VipsForeignSaveJp2k *jxl = (VipsForeignSaveJp2k *) object; + VipsForeignSaveJp2kFile *file = (VipsForeignSaveJp2kFile *) 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( VipsForeignSaveJp2kFileClass *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( VipsForeignSaveJp2kFile, filename ), + NULL ); + +} + +static void +vips_foreign_save_jxl_file_init( VipsForeignSaveJp2kFile *file ) +{ +} + +typedef struct _VipsForeignSaveJp2kBuffer { + VipsForeignSaveJp2k parent_object; + + /* Save to a buffer. + */ + VipsArea *buf; + +} VipsForeignSaveJp2kBuffer; + +typedef VipsForeignSaveJp2kClass VipsForeignSaveJp2kBufferClass; + +G_DEFINE_TYPE( VipsForeignSaveJp2kBuffer, vips_foreign_save_jxl_buffer, + vips_foreign_save_jxl_get_type() ); + +static int +vips_foreign_save_jxl_buffer_build( VipsObject *object ) +{ + VipsForeignSaveJp2k *jxl = (VipsForeignSaveJp2k *) object; + VipsForeignSaveJp2kBuffer *buffer = + (VipsForeignSaveJp2kBuffer *) 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( + VipsForeignSaveJp2kBufferClass *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( VipsForeignSaveJp2kBuffer, buf ), + VIPS_TYPE_BLOB ); + +} + +static void +vips_foreign_save_jxl_buffer_init( VipsForeignSaveJp2kBuffer *buffer ) +{ +} + +typedef struct _VipsForeignSaveJp2kTarget { + VipsForeignSaveJp2k parent_object; + + VipsTarget *target; +} VipsForeignSaveJp2kTarget; + +typedef VipsForeignSaveJp2kClass VipsForeignSaveJp2kTargetClass; + +G_DEFINE_TYPE( VipsForeignSaveJp2kTarget, vips_foreign_save_jxl_target, + vips_foreign_save_jxl_get_type() ); + +static int +vips_foreign_save_jxl_target_build( VipsObject *object ) +{ + VipsForeignSaveJp2k *jxl = (VipsForeignSaveJp2k *) object; + VipsForeignSaveJp2kTarget *target = + (VipsForeignSaveJp2kTarget *) 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( + VipsForeignSaveJp2kTargetClass *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( VipsForeignSaveJp2kTarget, target ), + VIPS_TYPE_TARGET ); + +} + +static void +vips_foreign_save_jxl_target_init( VipsForeignSaveJp2kTarget *target ) +{ +} + +#endif /*HAVE_LIBOPENJXL*/ + +/** + * vips_jxlsave: (method) + * @in: image to save + * @filename: file to write to + * @...: %NULL-terminated list of optional named arguments + * + * Write a VIPS image to a file in JPEG-XL format. + * + * See also: vips_image_write_to_file(), vips_jxlload(). + * + * 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 + * + * 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 + * + * 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/pforeign.h b/libvips/foreign/pforeign.h index 433bf1fe..0852d200 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -247,6 +247,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*/ From e93d0ab83cacc62ef0ad698e1e657e1445d2ab71 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 3 Apr 2021 15:03:43 +0100 Subject: [PATCH 07/23] jxlsave works at a basic level, anyway --- libvips/foreign/foreign.c | 2 +- libvips/foreign/jxlload.c | 4 +- libvips/foreign/jxlsave.c | 371 +++++++++++++------------------------- 3 files changed, 129 insertions(+), 248 deletions(-) diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index f039f7b4..843de451 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -2270,7 +2270,7 @@ vips_foreign_operation_init( void ) 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_source_get_type(); + vips_foreign_save_jxl_target_get_type(); #endif /*HAVE_LIBJXL*/ #ifdef HAVE_LIBOPENJP2 diff --git a/libvips/foreign/jxlload.c b/libvips/foreign/jxlload.c index 95186c30..ec4cd964 100644 --- a/libvips/foreign/jxlload.c +++ b/libvips/foreign/jxlload.c @@ -148,12 +148,12 @@ vips_foreign_load_jxl_build( VipsObject *object ) if( JxlDecoderSubscribeEvents( jxl->decoder, JXL_DEC_COLOR_ENCODING | JXL_DEC_BASIC_INFO | - JXL_DEC_FULL_IMAGE ) != JXL_DEC_SUCCESS ) { + JXL_DEC_FULL_IMAGE ) ) { vips_foreign_load_jxl_error( jxl, "JxlDecoderSubscribeEvents" ); return( -1 ); } if( JxlDecoderSetParallelRunner( jxl->decoder, - JxlThreadParallelRunner, jxl->runner ) != JXL_DEC_SUCCESS ) { + JxlThreadParallelRunner, jxl->runner ) ) { vips_foreign_load_jxl_error( jxl, "JxlDecoderSetParallelRunner" ); return( -1 ); diff --git a/libvips/foreign/jxlsave.c b/libvips/foreign/jxlsave.c index 18e2c3f6..169947b2 100644 --- a/libvips/foreign/jxlsave.c +++ b/libvips/foreign/jxlsave.c @@ -55,7 +55,9 @@ #include "pforeign.h" -typedef struct _VipsForeignSaveJp2k { +#define OUTPUT_BUFFER_SIZE (4096) + +typedef struct _VipsForeignSaveJxl { VipsForeignSave parent_object; /* Where to write (set by subclasses). @@ -65,6 +67,7 @@ typedef struct _VipsForeignSaveJp2k { /* Base image properties. */ JxlBasicInfo info; + JxlColorEncoding color_encoding; size_t icc_size; uint8_t *icc_data; @@ -73,17 +76,21 @@ typedef struct _VipsForeignSaveJp2k { void *runner; JxlEncoder *encoder; -} VipsForeignSaveJp2k; + /* Write buffer. + */ + uint8_t output_buffer[OUTPUT_BUFFER_SIZE]; -typedef VipsForeignSaveClass VipsForeignSaveJp2kClass; +} VipsForeignSaveJxl; -G_DEFINE_ABSTRACT_TYPE( VipsForeignSaveJp2k, vips_foreign_save_jxl, +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 ) { - VipsForeignSaveJp2k *jxl = (VipsForeignSaveJp2k *) gobject; + VipsForeignSaveJxl *jxl = (VipsForeignSaveJxl *) gobject; VIPS_FREEF( JxlThreadParallelRunnerDestroy, jxl->runner ); VIPS_FREEF( JxlEncoderDestroy, jxl->encoder ); @@ -92,242 +99,115 @@ vips_foreign_save_jxl_dispose( GObject *gobject ) 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 moemnt. + */ + vips_error( class->nickname, "%s", details ); +} + +static JxlPixelFormat vips_foreign_save_jxl_format = + {3, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0}; + static int vips_foreign_save_jxl_build( VipsObject *object ) { - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); VipsForeignSave *save = (VipsForeignSave *) object; - VipsForeignSaveJp2k *jxl = (VipsForeignSaveJp2k *) object; + VipsForeignSaveJxl *jxl = (VipsForeignSaveJxl *) object; - OPJ_COLOR_SPACE color_space; - int expected_bands; - int bits_per_pixel; - int i; - size_t sizeof_tile; - size_t sizeof_line; - VipsRect strip_position; + JxlEncoderOptions *encoder_options; + JxlEncoderStatus status; if( VIPS_OBJECT_CLASS( vips_foreign_save_jxl_parent_class )-> build( object ) ) return( -1 ); - opj_set_default_encoder_parameters( &jxl->parameters ); + jxl->runner = JxlThreadParallelRunnerCreate( NULL, + vips_concurrency_get() ); + jxl->encoder = JxlEncoderCreate( NULL ); - /* Analyze our arguments. - */ - - if( !vips_band_format_isint( save->ready->BandFmt ) ) { - vips_error( class->nickname, - "%s", _( "not an integer format" ) ); + if( JxlEncoderSetParallelRunner( jxl->encoder, + JxlThreadParallelRunner, jxl->runner ) ) { + vips_foreign_save_jxl_error( jxl, + "JxlDecoderSetParallelRunner" ); return( -1 ); } - switch( jxl->subsample_mode ) { - case VIPS_FOREIGN_SUBSAMPLE_AUTO: - jxl->downsample = - !jxl->lossless && - jxl->Q < 90 && - save->ready->Xsize % 2 == 0 && - save->ready->Ysize % 2 == 0 && - (save->ready->Type == VIPS_INTERPRETATION_sRGB || - save->ready->Type == VIPS_INTERPRETATION_RGB16) && - save->ready->Bands == 3; - break; - - case VIPS_FOREIGN_SUBSAMPLE_ON: - jxl->downsample = TRUE; - break; - - case VIPS_FOREIGN_SUBSAMPLE_OFF: - jxl->downsample = FALSE; - break; - - default: - g_assert_not_reached(); - break; + jxl->info.xsize = save->ready->Xsize; + jxl->info.ysize = save->ready->Ysize; + jxl->info.bits_per_sample = 32; + jxl->info.exponent_bits_per_sample = 8; + jxl->info.alpha_exponent_bits = 0; + jxl->info.alpha_bits = 0; + jxl->info.uses_original_profile = JXL_FALSE; + if( JxlEncoderSetBasicInfo( jxl->encoder, &jxl->info ) ) { + vips_foreign_save_jxl_error( jxl, "JxlEncoderSetBasicInfo" ); + return( -1 ); } - if( jxl->downsample ) - jxl->save_as_ycc = TRUE; - - /* CIELAB etc. do not seem to be well documented. - */ - switch( save->ready->Type ) { - case VIPS_INTERPRETATION_B_W: - case VIPS_INTERPRETATION_GREY16: - color_space = OPJ_CLRSPC_GRAY; - expected_bands = 1; - break; - - case VIPS_INTERPRETATION_sRGB: - case VIPS_INTERPRETATION_RGB16: - color_space = jxl->save_as_ycc ? - OPJ_CLRSPC_SYCC : OPJ_CLRSPC_SRGB; - expected_bands = 3; - break; - - case VIPS_INTERPRETATION_CMYK: - color_space = OPJ_CLRSPC_CMYK; - expected_bands = 4; - break; - - default: - color_space = OPJ_CLRSPC_UNSPECIFIED; - expected_bands = save->ready->Bands; - break; + JxlColorEncodingSetToSRGB( &jxl->color_encoding, + vips_foreign_save_jxl_format.num_channels < 3 ); + if( JxlEncoderSetColorEncoding( jxl->encoder, &jxl->color_encoding ) ) { + vips_foreign_save_jxl_error( jxl, + "JxlEncoderSetColorEncoding" ); + return( -1 ); } - switch( save->ready->BandFmt ) { - case VIPS_FORMAT_CHAR: - case VIPS_FORMAT_UCHAR: - bits_per_pixel = 8; - break; - - case VIPS_FORMAT_SHORT: - case VIPS_FORMAT_USHORT: - bits_per_pixel = 16; - break; - - case VIPS_FORMAT_INT: - case VIPS_FORMAT_UINT: - /* OpenJPEG only supports up to 31. - */ - bits_per_pixel = 31; - break; - - default: - g_assert_not_reached(); - break; + if( vips_image_wio_input( save->ready ) ) + return( -1 ); + + encoder_options = JxlEncoderOptionsCreate( jxl->encoder, NULL ); + if( JxlEncoderAddImageFrame( encoder_options, + &vips_foreign_save_jxl_format, + VIPS_IMAGE_ADDR( save->ready, 0, 0 ), + VIPS_IMAGE_SIZEOF_IMAGE( save->ready ) ) ) { + vips_foreign_save_jxl_error( jxl, "JxlEncoderAddImageFrame" ); + return( -1 ); } - /* Set parameters for compressor. - */ + do { + uint8_t *out; + size_t avail_out; - /* Always tile. - */ - jxl->parameters.tile_size_on = OPJ_TRUE; - jxl->parameters.cp_tdx = jxl->tile_width; - jxl->parameters.cp_tdy = jxl->tile_height; + 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; - /* Number of layers to write. Smallest layer is c. 2^5 on the smallest - * axis. - */ - jxl->parameters.numresolution = VIPS_MAX( 1, - log( VIPS_MIN( save->ready->Xsize, save->ready->Ysize ) ) / - log( 2 ) - 4 ); -#ifdef DEBUG - printf( "vips_foreign_save_jxl_build: numresolutions = %d\n", - jxl->parameters.numresolution ); -#endif /*DEBUG*/ - - for( i = 0; i < save->ready->Bands; i++ ) { - jxl->comps[i].dx = (jxl->downsample && i > 0) ? 2 : 1; - jxl->comps[i].dy = (jxl->downsample && i > 0) ? 2 : 1; - jxl->comps[i].w = save->ready->Xsize; - jxl->comps[i].h = save->ready->Ysize; - jxl->comps[i].x0 = 0; - jxl->comps[i].y0 = 0; - jxl->comps[i].prec = bits_per_pixel; - jxl->comps[i].bpp = bits_per_pixel; - jxl->comps[i].sgnd = - !vips_band_format_isuint( save->ready->BandFmt ); - } - - /* Makes three band images smaller, somehow. - */ - jxl->parameters.tcp_mct = - (save->ready->Bands == 3 && !jxl->downsample) ? 1 : 0; - - /* Lossy mode. - */ - if( !jxl->lossless ) { - jxl->parameters.irreversible = TRUE; - - /* Map Q to allowed distortion. - */ - jxl->parameters.cp_disto_alloc = 1; - jxl->parameters.cp_fixed_quality = TRUE; - jxl->parameters.tcp_distoratio[0] = jxl->Q; - jxl->parameters.tcp_numlayers = 1; - } - - /* Create output image. - */ - - jxl->image = opj_image_create( save->ready->Bands, - jxl->comps, color_space ); - jxl->image->x1 = save->ready->Xsize; - jxl->image->y1 = save->ready->Ysize; - - /* Tag alpha channels. - */ - for( i = 0; i < save->ready->Bands; i++ ) - jxl->image->comps[i].alpha = i >= expected_bands; - - /* Set up compressor. - */ - - jxl->codec = opj_create_compress( OPJ_CODEC_J2K ); - vips_foreign_save_jxl_attach_handlers( jxl, jxl->codec ); - if( !opj_setup_encoder( jxl->codec, &jxl->parameters, jxl->image ) ) - return( -1 ); - -#ifdef HAVE_LIBOPENJP2_THREADING - /* Use eg. VIPS_CONCURRENCY etc. to set n-cpus, if this openjpeg has - * stable support. - */ - opj_codec_set_threads( jxl->codec, vips_concurrency_get() ); -#endif /*HAVE_LIBOPENJP2_THREADING*/ - - if( !(jxl->stream = vips_foreign_save_jxl_target( jxl->target )) ) - return( -1 ); - - if( !opj_start_compress( jxl->codec, jxl->image, jxl->stream ) ) - return( -1 ); - - /* The buffer we repack tiles to for write. Large enough for one - * complete tile. - */ - sizeof_tile = VIPS_IMAGE_SIZEOF_PEL( save->ready ) * - jxl->tile_width * jxl->tile_height; - if( !(jxl->tile_buffer = VIPS_ARRAY( NULL, sizeof_tile, VipsPel )) ) - return( -1 ); - - /* We need a line of sums for chroma subsample. At worst, gint64. - */ - sizeof_line = sizeof( gint64 ) * jxl->tile_width; - if( !(jxl->accumulate = VIPS_ARRAY( NULL, sizeof_line, VipsPel )) ) - return( -1 ); - - /* The line of tiles we are building. - */ - jxl->strip = vips_region_new( save->ready ); - - /* Position strip at the top of the image, the height of a row of - * tiles. - */ - strip_position.left = 0; - strip_position.top = 0; - strip_position.width = save->ready->Xsize; - strip_position.height = jxl->tile_height; - if( vips_region_buffer( jxl->strip, &strip_position ) ) - return( -1 ); - - /* Write data. - */ - if( vips_sink_disc( save->ready, - vips_foreign_save_jxl_write_block, jxl ) ) - return( -1 ); - - opj_end_compress( jxl->codec, jxl->stream ); + default: + vips_foreign_save_jxl_error( jxl, + "JxlEncoderProcessOutput" ); + return( -1 ); + } + } while( status != JXL_ENC_SUCCESS ); vips_target_finish( jxl->target ); return( 0 ); } +#define F VIPS_FORMAT_FLOAT + +/* Type promotion for save ... just always go to uchar. + */ +static int bandfmt_jpeg[10] = { + /* UC C US S UI I F X D DX */ + F, F, F, F, F, F, F, F, F, F +}; + static void -vips_foreign_save_jxl_class_init( VipsForeignSaveJp2kClass *class ) +vips_foreign_save_jxl_class_init( VipsForeignSaveJxlClass *class ) { GObjectClass *gobject_class = G_OBJECT_CLASS( class ); VipsObjectClass *object_class = (VipsObjectClass *) class; @@ -345,33 +225,34 @@ vips_foreign_save_jxl_class_init( VipsForeignSaveJp2kClass *class ) foreign_class->suffs = vips__jxl_suffs; save_class->saveable = VIPS_SAVEABLE_ANY; + save_class->format_table = bandfmt_jpeg; } static void -vips_foreign_save_jxl_init( VipsForeignSaveJp2k *jxl ) +vips_foreign_save_jxl_init( VipsForeignSaveJxl *jxl ) { } -typedef struct _VipsForeignSaveJp2kFile { - VipsForeignSaveJp2k parent_object; +typedef struct _VipsForeignSaveJxlFile { + VipsForeignSaveJxl parent_object; /* Filename for save. */ char *filename; -} VipsForeignSaveJp2kFile; +} VipsForeignSaveJxlFile; -typedef VipsForeignSaveJp2kClass VipsForeignSaveJp2kFileClass; +typedef VipsForeignSaveJxlClass VipsForeignSaveJxlFileClass; -G_DEFINE_TYPE( VipsForeignSaveJp2kFile, vips_foreign_save_jxl_file, +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 ) { - VipsForeignSaveJp2k *jxl = (VipsForeignSaveJp2k *) object; - VipsForeignSaveJp2kFile *file = (VipsForeignSaveJp2kFile *) object; + VipsForeignSaveJxl *jxl = (VipsForeignSaveJxl *) object; + VipsForeignSaveJxlFile *file = (VipsForeignSaveJxlFile *) object; if( !(jxl->target = vips_target_new_to_file( file->filename )) ) return( -1 ); @@ -384,7 +265,7 @@ vips_foreign_save_jxl_file_build( VipsObject *object ) } static void -vips_foreign_save_jxl_file_class_init( VipsForeignSaveJp2kFileClass *class ) +vips_foreign_save_jxl_file_class_init( VipsForeignSaveJxlFileClass *class ) { GObjectClass *gobject_class = G_OBJECT_CLASS( class ); VipsObjectClass *object_class = (VipsObjectClass *) class; @@ -399,36 +280,36 @@ vips_foreign_save_jxl_file_class_init( VipsForeignSaveJp2kFileClass *class ) _( "Filename" ), _( "Filename to load from" ), VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignSaveJp2kFile, filename ), + G_STRUCT_OFFSET( VipsForeignSaveJxlFile, filename ), NULL ); } static void -vips_foreign_save_jxl_file_init( VipsForeignSaveJp2kFile *file ) +vips_foreign_save_jxl_file_init( VipsForeignSaveJxlFile *file ) { } -typedef struct _VipsForeignSaveJp2kBuffer { - VipsForeignSaveJp2k parent_object; +typedef struct _VipsForeignSaveJxlBuffer { + VipsForeignSaveJxl parent_object; /* Save to a buffer. */ VipsArea *buf; -} VipsForeignSaveJp2kBuffer; +} VipsForeignSaveJxlBuffer; -typedef VipsForeignSaveJp2kClass VipsForeignSaveJp2kBufferClass; +typedef VipsForeignSaveJxlClass VipsForeignSaveJxlBufferClass; -G_DEFINE_TYPE( VipsForeignSaveJp2kBuffer, vips_foreign_save_jxl_buffer, +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 ) { - VipsForeignSaveJp2k *jxl = (VipsForeignSaveJp2k *) object; - VipsForeignSaveJp2kBuffer *buffer = - (VipsForeignSaveJp2kBuffer *) object; + VipsForeignSaveJxl *jxl = (VipsForeignSaveJxl *) object; + VipsForeignSaveJxlBuffer *buffer = + (VipsForeignSaveJxlBuffer *) object; VipsBlob *blob; @@ -448,7 +329,7 @@ vips_foreign_save_jxl_buffer_build( VipsObject *object ) static void vips_foreign_save_jxl_buffer_class_init( - VipsForeignSaveJp2kBufferClass *class ) + VipsForeignSaveJxlBufferClass *class ) { GObjectClass *gobject_class = G_OBJECT_CLASS( class ); VipsObjectClass *object_class = (VipsObjectClass *) class; @@ -463,33 +344,33 @@ vips_foreign_save_jxl_buffer_class_init( _( "Buffer" ), _( "Buffer to save to" ), VIPS_ARGUMENT_REQUIRED_OUTPUT, - G_STRUCT_OFFSET( VipsForeignSaveJp2kBuffer, buf ), + G_STRUCT_OFFSET( VipsForeignSaveJxlBuffer, buf ), VIPS_TYPE_BLOB ); } static void -vips_foreign_save_jxl_buffer_init( VipsForeignSaveJp2kBuffer *buffer ) +vips_foreign_save_jxl_buffer_init( VipsForeignSaveJxlBuffer *buffer ) { } -typedef struct _VipsForeignSaveJp2kTarget { - VipsForeignSaveJp2k parent_object; +typedef struct _VipsForeignSaveJxlTarget { + VipsForeignSaveJxl parent_object; VipsTarget *target; -} VipsForeignSaveJp2kTarget; +} VipsForeignSaveJxlTarget; -typedef VipsForeignSaveJp2kClass VipsForeignSaveJp2kTargetClass; +typedef VipsForeignSaveJxlClass VipsForeignSaveJxlTargetClass; -G_DEFINE_TYPE( VipsForeignSaveJp2kTarget, vips_foreign_save_jxl_target, +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 ) { - VipsForeignSaveJp2k *jxl = (VipsForeignSaveJp2k *) object; - VipsForeignSaveJp2kTarget *target = - (VipsForeignSaveJp2kTarget *) object; + VipsForeignSaveJxl *jxl = (VipsForeignSaveJxl *) object; + VipsForeignSaveJxlTarget *target = + (VipsForeignSaveJxlTarget *) object; if( target->target ) { jxl->target = target->target; @@ -505,7 +386,7 @@ vips_foreign_save_jxl_target_build( VipsObject *object ) static void vips_foreign_save_jxl_target_class_init( - VipsForeignSaveJp2kTargetClass *class ) + VipsForeignSaveJxlTargetClass *class ) { GObjectClass *gobject_class = G_OBJECT_CLASS( class ); VipsObjectClass *object_class = (VipsObjectClass *) class; @@ -520,13 +401,13 @@ vips_foreign_save_jxl_target_class_init( _( "Target" ), _( "Target to save to" ), VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignSaveJp2kTarget, target ), + G_STRUCT_OFFSET( VipsForeignSaveJxlTarget, target ), VIPS_TYPE_TARGET ); } static void -vips_foreign_save_jxl_target_init( VipsForeignSaveJp2kTarget *target ) +vips_foreign_save_jxl_target_init( VipsForeignSaveJxlTarget *target ) { } From b82ca537e730cddde4e20a128d640c9c0e7c478c Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 3 Apr 2021 16:32:02 +0100 Subject: [PATCH 08/23] set basic jxl image metadata from vips --- libvips/foreign/jxlsave.c | 128 +++++++++++++++++++++++++++++++++----- 1 file changed, 113 insertions(+), 15 deletions(-) diff --git a/libvips/foreign/jxlsave.c b/libvips/foreign/jxlsave.c index 169947b2..2cec0dd1 100644 --- a/libvips/foreign/jxlsave.c +++ b/libvips/foreign/jxlsave.c @@ -55,6 +55,15 @@ #include "pforeign.h" +/* TODO: + * + * - libjxl currently seems to be missing API to attach a profile + * + * - libjxl seems to only work in one shot mode, so there's no way to write in + * chunks + * + */ + #define OUTPUT_BUFFER_SIZE (4096) typedef struct _VipsForeignSaveJxl { @@ -68,8 +77,7 @@ typedef struct _VipsForeignSaveJxl { */ JxlBasicInfo info; JxlColorEncoding color_encoding; - size_t icc_size; - uint8_t *icc_data; + JxlPixelFormat format; /* Encoder state. */ @@ -109,9 +117,6 @@ vips_foreign_save_jxl_error( VipsForeignSaveJxl *jxl, const char *details ) vips_error( class->nickname, "%s", details ); } -static JxlPixelFormat vips_foreign_save_jxl_format = - {3, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0}; - static int vips_foreign_save_jxl_build( VipsObject *object ) { @@ -136,32 +141,120 @@ vips_foreign_save_jxl_build( VipsObject *object ) 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->info.bits_per_sample = 32; - jxl->info.exponent_bits_per_sample = 8; - jxl->info.alpha_exponent_bits = 0; - jxl->info.alpha_bits = 0; + 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, - vips_foreign_save_jxl_format.num_channels < 3 ); + 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. + */ if( vips_image_wio_input( save->ready ) ) return( -1 ); encoder_options = JxlEncoderOptionsCreate( jxl->encoder, NULL ); - if( JxlEncoderAddImageFrame( encoder_options, - &vips_foreign_save_jxl_format, + if( JxlEncoderAddImageFrame( encoder_options, &jxl->format, VIPS_IMAGE_ADDR( save->ready, 0, 0 ), VIPS_IMAGE_SIZEOF_IMAGE( save->ready ) ) ) { vips_foreign_save_jxl_error( jxl, "JxlEncoderAddImageFrame" ); @@ -197,13 +290,18 @@ vips_foreign_save_jxl_build( VipsObject *object ) 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 ... just always go to uchar. +/* Type promotion for save ... unsigned ints + float + double. */ static int bandfmt_jpeg[10] = { - /* UC C US S UI I F X D DX */ - F, F, F, F, F, F, F, F, F, F + /* UC C US S UI I F X D DX */ + UC, UC, US, US, UI, UI, F, F, F, F }; static void From 55e634a0d2de984cc7a9002e18c0449bd0752c65 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 3 Apr 2021 17:50:47 +0100 Subject: [PATCH 09/23] add support for native jxl decode map jxl native formats to libvips iamge formats --- libvips/foreign/jxlload.c | 105 +++++++++++++++++++++++++++++++++----- 1 file changed, 93 insertions(+), 12 deletions(-) diff --git a/libvips/foreign/jxlload.c b/libvips/foreign/jxlload.c index ec4cd964..260da3c5 100644 --- a/libvips/foreign/jxlload.c +++ b/libvips/foreign/jxlload.c @@ -73,6 +73,7 @@ typedef struct _VipsForeignLoadJxl { /* Base image properties. */ JxlBasicInfo info; + JxlPixelFormat format; size_t icc_size; uint8_t *icc_data; @@ -182,11 +183,6 @@ vips_foreign_load_jxl_get_flags( VipsForeignLoad *load ) return( VIPS_FOREIGN_PARTIAL ); } -/* Always read as scRGBA. - */ -static JxlPixelFormat vips_foreign_load_jxl_format = - { 4, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0 }; - static int vips_foreign_load_jxl_fill_input( VipsForeignLoadJxl *jxl, size_t bytes_remaining ) @@ -337,6 +333,74 @@ vips_foreign_load_jxl_print_info( VipsForeignLoadJxl *jxl ) 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. @@ -344,8 +408,8 @@ vips_foreign_load_jxl_set_header( VipsForeignLoadJxl *jxl, VipsImage *out ) vips_image_pipelinev( out, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); vips_image_init_fields( out, - jxl->info.xsize, jxl->info.ysize, 4, VIPS_FORMAT_FLOAT, - VIPS_CODING_NONE, VIPS_INTERPRETATION_scRGB, 1.0, 1.0 ); + 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 ) { @@ -392,11 +456,29 @@ vips_foreign_load_jxl_header( VipsForeignLoad *load ) return( -1 ); } vips_foreign_load_jxl_print_info( jxl ); + + /* 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; + break; case JXL_DEC_COLOR_ENCODING: if( JxlDecoderGetICCProfileSize( jxl->decoder, - &vips_foreign_load_jxl_format, + &jxl->format, JXL_COLOR_PROFILE_TARGET_DATA, &jxl->icc_size ) ) { vips_foreign_load_jxl_error( jxl, @@ -407,9 +489,8 @@ vips_foreign_load_jxl_header( VipsForeignLoad *load ) jxl->icc_size )) ) return( -1 ); - if( JxlDecoderGetColorAsICCProfile( jxl->decoder, - &vips_foreign_load_jxl_format, + &jxl->format, JXL_COLOR_PROFILE_TARGET_DATA, jxl->icc_data, jxl->icc_size ) ) { vips_foreign_load_jxl_error( jxl, @@ -465,7 +546,7 @@ vips_foreign_load_jxl_load( VipsForeignLoad *load ) return( -1 ); if( JxlDecoderImageOutBufferSize( jxl->decoder, - &vips_foreign_load_jxl_format, + &jxl->format, &buffer_size ) ) { vips_foreign_load_jxl_error( jxl, "JxlDecoderImageOutBufferSize" ); @@ -478,7 +559,7 @@ vips_foreign_load_jxl_load( VipsForeignLoad *load ) return( -1 ); } if( JxlDecoderSetImageOutBuffer( jxl->decoder, - &vips_foreign_load_jxl_format, + &jxl->format, VIPS_IMAGE_ADDR( t[0], 0, 0 ), VIPS_IMAGE_SIZEOF_IMAGE( t[0] ) ) ) { vips_foreign_load_jxl_error( jxl, From 70078e3774f9f62799f9fac44cc5aa4e1a802890 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 3 Apr 2021 18:11:38 +0100 Subject: [PATCH 10/23] add jxl encoder options though they don't seem to affect file size, curiously --- libvips/foreign/jxlload.c | 14 ++++++- libvips/foreign/jxlsave.c | 83 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 92 insertions(+), 5 deletions(-) diff --git a/libvips/foreign/jxlload.c b/libvips/foreign/jxlload.c index 260da3c5..42c03f0c 100644 --- a/libvips/foreign/jxlload.c +++ b/libvips/foreign/jxlload.c @@ -32,9 +32,9 @@ */ /* - */ #define DEBUG_VERBOSE #define DEBUG + */ #ifdef HAVE_CONFIG_H #include @@ -56,6 +56,18 @@ #include "pforeign.h" +/* TODO: + * + * - libjxl seems to only work in one shot mode, so there's no way to read in + * chunks + * + * - preview image? EXIF? XMP? + * + * - check scRGB encoding + * + * - add "shrink" option to read out 8x shrunk image? + */ + #define INPUT_BUFFER_SIZE (4096) typedef struct _VipsForeignLoadJxl { diff --git a/libvips/foreign/jxlsave.c b/libvips/foreign/jxlsave.c index 2cec0dd1..9895551d 100644 --- a/libvips/foreign/jxlsave.c +++ b/libvips/foreign/jxlsave.c @@ -62,6 +62,9 @@ * - libjxl seems to only work in one shot mode, so there's no way to write in * chunks * + * - embed a preview image? EXIF? XMP? + * + * - check scRGB encoding */ #define OUTPUT_BUFFER_SIZE (4096) @@ -73,6 +76,13 @@ typedef struct _VipsForeignSaveJxl { */ VipsTarget *target; + /* Encoder options. + */ + int tier; + double distance; + int effort; + gboolean lossless; + /* Base image properties. */ JxlBasicInfo info; @@ -123,7 +133,7 @@ vips_foreign_save_jxl_build( VipsObject *object ) VipsForeignSave *save = (VipsForeignSave *) object; VipsForeignSaveJxl *jxl = (VipsForeignSaveJxl *) object; - JxlEncoderOptions *encoder_options; + JxlEncoderOptions *options; JxlEncoderStatus status; if( VIPS_OBJECT_CLASS( vips_foreign_save_jxl_parent_class )-> @@ -253,8 +263,13 @@ vips_foreign_save_jxl_build( VipsObject *object ) if( vips_image_wio_input( save->ready ) ) return( -1 ); - encoder_options = JxlEncoderOptionsCreate( jxl->encoder, NULL ); - if( JxlEncoderAddImageFrame( encoder_options, &jxl->format, + options = JxlEncoderOptionsCreate( jxl->encoder, NULL ); + JxlEncoderOptionsSetDecodingSpeed( options, jxl->tier ); + JxlEncoderOptionsSetDistance( options, jxl->distance ); + JxlEncoderOptionsSetEffort( options, jxl->effort ); + JxlEncoderOptionsSetLossless( options, jxl->lossless ); + + 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" ); @@ -325,11 +340,43 @@ vips_foreign_save_jxl_class_init( VipsForeignSaveJxlClass *class ) 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 ); + } static void vips_foreign_save_jxl_init( VipsForeignSaveJxl *jxl ) { + jxl->tier = 0; + jxl->distance = 1.0; + jxl->effort = 7; + jxl->lossless = FALSE; } typedef struct _VipsForeignSaveJxlFile { @@ -517,9 +564,23 @@ vips_foreign_save_jxl_target_init( VipsForeignSaveJxlTarget *target ) * @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 + * * Write a VIPS image to a file in JPEG-XL format. * - * See also: vips_image_write_to_file(), vips_jxlload(). + * @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). + * + * Set @lossless to enable lossless compresion. * * Returns: 0 on success, -1 on error. */ @@ -543,6 +604,13 @@ vips_jxlsave( VipsImage *in, const char *filename, ... ) * @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 + * * As vips_jxlsave(), but save to a memory buffer. * * See also: vips_jxlsave(), vips_image_write_to_target(). @@ -583,6 +651,13 @@ vips_jxlsave_buffer( VipsImage *in, void **buf, size_t *len, ... ) * @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 + * * As vips_jxlsave(), but save to a target. * * See also: vips_jxlsave(), vips_image_write_to_target(). From 209f2c514421700783aee38d12325fb5d69eb899 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 3 Apr 2021 18:40:21 +0100 Subject: [PATCH 11/23] tidy up --- ChangeLog | 1 + libvips/foreign/jxlload.c | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/ChangeLog b/ChangeLog index 67264ff7..1c9e3d1f 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 22/12/20 start 8.10.6 - don't seek on bad file descriptors [kleisauke] diff --git a/libvips/foreign/jxlload.c b/libvips/foreign/jxlload.c index 42c03f0c..3abef684 100644 --- a/libvips/foreign/jxlload.c +++ b/libvips/foreign/jxlload.c @@ -66,6 +66,8 @@ * - check scRGB encoding * * - add "shrink" option to read out 8x shrunk image? + * + * - add animation support */ #define INPUT_BUFFER_SIZE (4096) @@ -214,6 +216,7 @@ vips_foreign_load_jxl_fill_input( VipsForeignLoadJxl *jxl, return( 0 ); } +#ifdef DEBUG static void vips_foreign_load_jxl_print_status( JxlDecoderStatus status ) { @@ -282,6 +285,7 @@ vips_foreign_load_jxl_print_status( JxlDecoderStatus status ) g_assert_not_reached(); } } +#endif /*DEBUG*/ static JxlDecoderStatus vips_foreign_load_jxl_process( VipsForeignLoadJxl *jxl ) @@ -300,12 +304,15 @@ vips_foreign_load_jxl_process( VipsForeignLoadJxl *jxl ) 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 ); } +#ifdef DEBUG static void vips_foreign_load_jxl_print_info( VipsForeignLoadJxl *jxl ) { @@ -341,6 +348,7 @@ vips_foreign_load_jxl_print_info( VipsForeignLoadJxl *jxl ) printf( " animation.have_timecodes = %d\n", jxl->info.animation.have_timecodes ); } +#endif /*DEBUG*/ static int vips_foreign_load_jxl_set_header( VipsForeignLoadJxl *jxl, VipsImage *out ) @@ -432,6 +440,9 @@ vips_foreign_load_jxl_set_header( VipsForeignLoadJxl *jxl, VipsImage *out ) jxl->icc_size = 0; } + vips_image_set_int( out, + VIPS_META_ORIENTATION, jxl->info.orientation ); + return( 0 ); } @@ -467,7 +478,9 @@ vips_foreign_load_jxl_header( VipsForeignLoad *load ) "JxlDecoderGetBasicInfo" ); return( -1 ); } +#ifdef DEBUG vips_foreign_load_jxl_print_info( jxl ); +#endif /*DEBUG*/ /* Pick a pixel format to decode to. */ From 633d35f4aecb0fc0dd51524b4110f0a6c1c9f18f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 14 Apr 2021 12:29:38 +0100 Subject: [PATCH 12/23] add Q param to jxl save for convenience --- libvips/foreign/jxlsave.c | 41 +++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/libvips/foreign/jxlsave.c b/libvips/foreign/jxlsave.c index 9895551d..dec638af 100644 --- a/libvips/foreign/jxlsave.c +++ b/libvips/foreign/jxlsave.c @@ -1,7 +1,7 @@ /* save as jpeg2000 * * 18/3/20 - * - from jxlload.c + * - from heifload.c */ /* @@ -33,8 +33,8 @@ /* #define DEBUG_VERBOSE -#define DEBUG */ +#define DEBUG #ifdef HAVE_CONFIG_H #include @@ -59,10 +59,10 @@ * * - libjxl currently seems to be missing API to attach a profile * - * - libjxl seems to only work in one shot mode, so there's no way to write in + * - libjxl encode only works in one shot mode, so there's no way to write in * chunks * - * - embed a preview image? EXIF? XMP? + * - embed a preview image? EXIF? XMP? api for this is on the way * * - check scRGB encoding */ @@ -82,6 +82,7 @@ typedef struct _VipsForeignSaveJxl { double distance; int effort; gboolean lossless; + int Q; /* Base image properties. */ @@ -140,6 +141,13 @@ vips_foreign_save_jxl_build( VipsObject *object ) build( object ) ) return( -1 ); + /* If Q is set and distance is not, use Q to set a rough distance + * value. Q 75 is distance 1.0, Q 100 is distance 0.0. + */ + if( vips_object_argument_isset( object, "Q" ) && + !vips_object_argument_isset( object, "distance" ) ) + jxl->distance = (100 - jxl->Q) / 25.0; + jxl->runner = JxlThreadParallelRunnerCreate( NULL, vips_concurrency_get() ); jxl->encoder = JxlEncoderCreate( NULL ); @@ -258,17 +266,25 @@ vips_foreign_save_jxl_build( VipsObject *object ) } /* Render the entire image in memory. libjxl seems to be missing - * tile-based write. + * 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 + printf( "jxl encode options:\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 ) ) ) { @@ -368,6 +384,13 @@ vips_foreign_save_jxl_class_init( VipsForeignSaveJxlClass *class ) 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 @@ -570,6 +593,7 @@ vips_foreign_save_jxl_target_init( VipsForeignSaveJxlTarget *target ) * * @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. * @@ -580,6 +604,9 @@ vips_foreign_save_jxl_target_init( VipsForeignSaveJxlTarget *target ) * (highest quality), and maximum is 15 (lowest quality). Default is 1.0 * (visually lossless). * + * As a convenience, you can use @Q to set @distance. @Q of 75 is distance + * 1.0, @Q of 100 is distance 0.0. + * * Set @lossless to enable lossless compresion. * * Returns: 0 on success, -1 on error. @@ -610,6 +637,7 @@ vips_jxlsave( VipsImage *in, const char *filename, ... ) * * @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. * @@ -657,6 +685,7 @@ vips_jxlsave_buffer( VipsImage *in, void **buf, size_t *len, ... ) * * @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. * From f4046a95f199aab3ea7da8498dfa6afb6764904b Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 14 Apr 2021 12:50:09 +0100 Subject: [PATCH 13/23] more dbg output for jxlsave --- libvips/foreign/jxlload.c | 6 ++-- libvips/foreign/jxlsave.c | 62 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/libvips/foreign/jxlload.c b/libvips/foreign/jxlload.c index 3abef684..b24fc638 100644 --- a/libvips/foreign/jxlload.c +++ b/libvips/foreign/jxlload.c @@ -63,11 +63,11 @@ * * - preview image? EXIF? XMP? * - * - check scRGB encoding - * - * - add "shrink" option to read out 8x shrunk image? + * - check scRGB load * * - add animation support + * + * - add "shrink" option to read out 8x shrunk image? */ #define INPUT_BUFFER_SIZE (4096) diff --git a/libvips/foreign/jxlsave.c b/libvips/foreign/jxlsave.c index dec638af..9fe24bd1 100644 --- a/libvips/foreign/jxlsave.c +++ b/libvips/foreign/jxlsave.c @@ -62,9 +62,11 @@ * - libjxl encode only works in one shot mode, so there's no way to write in * chunks * - * - embed a preview image? EXIF? XMP? api for this is on the way + * - add metadata support EXIF, XMP, etc. api for this is on the way * - * - check scRGB encoding + * - add animation support + * + * - libjxl is currently missing error messages (I think) */ #define OUTPUT_BUFFER_SIZE (4096) @@ -128,6 +130,58 @@ vips_foreign_save_jxl_error( VipsForeignSaveJxl *jxl, const char *details ) vips_error( class->nickname, "%s", details ); } +#ifdef DEBUG +static void +vips_foreign_save_jxl_print_info( JxlBasicInfo *info ) +{ + printf( "JxlBasicInfo:\n" ); + printf( " xsize = %d\n", info->xsize ); + printf( " ysize = %d\n", info->ysize ); + printf( " num_color_channels = %d\n", info->num_color_channels ); + printf( " num_extra_channels = %d\n", info->num_extra_channels ); + printf( " bits_per_sample = %d\n", info->bits_per_sample ); + printf( " exponent_bits_per_sample = %d\n", + info->exponent_bits_per_sample ); + printf( " alpha_bits = %d\n", info->alpha_bits ); + printf( " alpha_exponent_bits = %d\n", info->alpha_exponent_bits ); + printf( " intensity_target = %g\n", info->intensity_target ); + printf( " uses_original_profile = %d\n", + info->uses_original_profile ); +} + +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 ); +} +#endif /*DEBUG*/ + static int vips_foreign_save_jxl_build( VipsObject *object ) { @@ -278,7 +332,9 @@ vips_foreign_save_jxl_build( VipsObject *object ) JxlEncoderOptionsSetLossless( options, jxl->lossless ); #ifdef DEBUG - printf( "jxl encode options:\n" ); + 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 ); From c48a82d9374ce6e21f5966b5d46d592010882492 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 14 Apr 2021 16:39:57 +0100 Subject: [PATCH 14/23] cleanups --- libvips/foreign/jxlload.c | 6 +++--- libvips/foreign/jxlsave.c | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/libvips/foreign/jxlload.c b/libvips/foreign/jxlload.c index ef03f5dc..5cac3c90 100644 --- a/libvips/foreign/jxlload.c +++ b/libvips/foreign/jxlload.c @@ -1,7 +1,7 @@ /* load jpeg-xl * * 18/3/20 - * - from jxlload.c + * - from heifload.c */ /* @@ -60,11 +60,11 @@ * * - add metadata support * - * - check scRGB load - * * - add animation support * * - add "shrink" option to read out 8x shrunk image? + * + * - fix scRGB gamma */ #define INPUT_BUFFER_SIZE (4096) diff --git a/libvips/foreign/jxlsave.c b/libvips/foreign/jxlsave.c index 471b3758..5e72bf8b 100644 --- a/libvips/foreign/jxlsave.c +++ b/libvips/foreign/jxlsave.c @@ -67,6 +67,8 @@ * - add animation support * * - libjxl is currently missing error messages (I think) + * + * - fix scRGB gamma */ #define OUTPUT_BUFFER_SIZE (4096) From 2a51cf23a3caf193a467e21df011a7dbaadb262c Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 14 Apr 2021 17:34:30 +0100 Subject: [PATCH 15/23] better Q computation stolen from cjxl.cc --- libvips/foreign/jxlsave.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/libvips/foreign/jxlsave.c b/libvips/foreign/jxlsave.c index 5e72bf8b..e3586820 100644 --- a/libvips/foreign/jxlsave.c +++ b/libvips/foreign/jxlsave.c @@ -215,11 +215,13 @@ vips_foreign_save_jxl_build( VipsObject *object ) return( -1 ); /* If Q is set and distance is not, use Q to set a rough distance - * value. Q 75 is distance 1.0, Q 100 is distance 0.0. + * value. Formula stolen from cjxl.c and very roughly approximates + * libjpeg values. */ - if( vips_object_argument_isset( object, "Q" ) && - !vips_object_argument_isset( object, "distance" ) ) - jxl->distance = (100 - jxl->Q) / 25.0; + 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; jxl->runner = JxlThreadParallelRunnerCreate( NULL, vips_concurrency_get() ); @@ -475,6 +477,7 @@ vips_foreign_save_jxl_init( VipsForeignSaveJxl *jxl ) jxl->distance = 1.0; jxl->effort = 7; jxl->lossless = FALSE; + jxl->Q = 75; } typedef struct _VipsForeignSaveJxlFile { From b1abb2d8241b1976f8eb5b5bb7ffe5e4155c9c22 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 15 Apr 2021 12:55:14 +0100 Subject: [PATCH 16/23] comment spelling --- libvips/foreign/jxlload.c | 2 +- libvips/foreign/jxlsave.c | 51 ++++++++++++++++++++------------------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/libvips/foreign/jxlload.c b/libvips/foreign/jxlload.c index 5cac3c90..fe032c1c 100644 --- a/libvips/foreign/jxlload.c +++ b/libvips/foreign/jxlload.c @@ -139,7 +139,7 @@ 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 moemnt. + /* TODO ... jxl has no way to get error messages at the moment. */ vips_error( class->nickname, "%s", details ); } diff --git a/libvips/foreign/jxlsave.c b/libvips/foreign/jxlsave.c index e3586820..efd043d3 100644 --- a/libvips/foreign/jxlsave.c +++ b/libvips/foreign/jxlsave.c @@ -127,7 +127,7 @@ 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 moemnt. + /* TODO ... jxl has no way to get error messages at the moment. */ vips_error( class->nickname, "%s", details ); } @@ -137,34 +137,35 @@ 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", + 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", + 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", + 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", + 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", + printf( " animation.num_loops = %d\n", info->animation.num_loops ); + printf( " animation.have_timecodes = %d\n", info->animation.have_timecodes ); } From 0cdfb851b9723c9a3da9e9683a6dc24a978f7798 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 18 Apr 2021 17:53:15 +0100 Subject: [PATCH 17/23] incorporate comments from @lovell review --- configure.ac | 2 +- libvips/foreign/jxlload.c | 15 +++++++++------ libvips/foreign/jxlsave.c | 11 +++++++---- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/configure.ac b/configure.ac index de04338b..b3c5a07c 100644 --- a/configure.ac +++ b/configure.ac @@ -778,7 +778,7 @@ AC_ARG_WITH([libjxl], 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 >=2.2 installed.]) + [define if you have libjxl >= 0.3.7 installed.]) with_libjxl=yes PACKAGES_USED="$PACKAGES_USED libjxl" ], diff --git a/libvips/foreign/jxlload.c b/libvips/foreign/jxlload.c index fe032c1c..cf3af215 100644 --- a/libvips/foreign/jxlload.c +++ b/libvips/foreign/jxlload.c @@ -33,8 +33,8 @@ /* #define DEBUG_VERBOSE - */ #define DEBUG + */ #ifdef HAVE_CONFIG_H #include @@ -359,11 +359,11 @@ vips_foreign_load_jxl_process( VipsForeignLoadJxl *jxl ) while( (status = JxlDecoderProcessInput( jxl->decoder )) == JXL_DEC_NEED_MORE_INPUT ) { - size_t unused; + size_t bytes_remaining; - unused = JxlDecoderReleaseInput( jxl->decoder ); - if( vips_foreign_load_jxl_fill_input( jxl, unused ) && - unused == 0 ) + 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 ); @@ -897,6 +897,9 @@ vips_foreign_load_jxl_source_init( * * 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. @@ -921,7 +924,7 @@ vips_jxlload( const char *filename, VipsImage **out, ... ) * @out: (out): image to write * @...: %NULL-terminated list of optional named arguments * - * Exactly as vips_jxlload(), but read from a source. + * Exactly as vips_jxlload(), but read from a buffer. * * Returns: 0 on success, -1 on error. */ diff --git a/libvips/foreign/jxlsave.c b/libvips/foreign/jxlsave.c index efd043d3..7cd74005 100644 --- a/libvips/foreign/jxlsave.c +++ b/libvips/foreign/jxlsave.c @@ -1,4 +1,4 @@ -/* save as jpeg2000 +/* save as jpeg-xl * * 18/3/20 * - from heifload.c @@ -33,8 +33,8 @@ /* #define DEBUG_VERBOSE - */ #define DEBUG + */ #ifdef HAVE_CONFIG_H #include @@ -676,6 +676,9 @@ vips_foreign_save_jxl_target_init( VipsForeignSaveJxlTarget *target ) * * 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. * @@ -683,8 +686,8 @@ vips_foreign_save_jxl_target_init( VipsForeignSaveJxlTarget *target ) * (highest quality), and maximum is 15 (lowest quality). Default is 1.0 * (visually lossless). * - * As a convenience, you can use @Q to set @distance. @Q of 75 is distance - * 1.0, @Q of 100 is distance 0.0. + * 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. * From 63d2da6a1d205d9c2027d0b826df5bbb6c05e03b Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 18 Apr 2021 17:57:09 +0100 Subject: [PATCH 18/23] note JPEG-XL in README.md --- README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) 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 From 4e28039c02c909c48a3ef524d6a4584bf62f9ccc Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 19 Apr 2021 10:12:28 +0100 Subject: [PATCH 19/23] fix distance 0 save, better error messages --- libvips/foreign/jxlload.c | 5 +++-- libvips/foreign/jxlsave.c | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/libvips/foreign/jxlload.c b/libvips/foreign/jxlload.c index cf3af215..7dca7e2e 100644 --- a/libvips/foreign/jxlload.c +++ b/libvips/foreign/jxlload.c @@ -141,7 +141,7 @@ vips_foreign_load_jxl_error( VipsForeignLoadJxl *jxl, const char *details ) /* TODO ... jxl has no way to get error messages at the moment. */ - vips_error( class->nickname, "%s", details ); + vips_error( class->nickname, "error %s", details ); } static int @@ -279,7 +279,8 @@ vips_foreign_load_jxl_print_status( JxlDecoderStatus status ) break; default: - g_assert_not_reached(); + printf( "JXL_DEC_\n" ); + break; } } diff --git a/libvips/foreign/jxlsave.c b/libvips/foreign/jxlsave.c index 7cd74005..56a26541 100644 --- a/libvips/foreign/jxlsave.c +++ b/libvips/foreign/jxlsave.c @@ -129,7 +129,7 @@ vips_foreign_save_jxl_error( VipsForeignSaveJxl *jxl, const char *details ) /* TODO ... jxl has no way to get error messages at the moment. */ - vips_error( class->nickname, "%s", details ); + vips_error( class->nickname, "error %s", details ); } #ifdef DEBUG @@ -200,6 +200,32 @@ vips_foreign_save_jxl_print_format( JxlPixelFormat *format ) 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 @@ -224,6 +250,11 @@ vips_foreign_save_jxl_build( VipsObject *object ) 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 ); @@ -390,6 +421,9 @@ vips_foreign_save_jxl_build( VipsObject *object ) 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 ); From fa6e403d3055b30a6eeda275ea39488dd3880a9f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 22 Apr 2021 15:23:20 +0100 Subject: [PATCH 20/23] tiny formatting improvement --- libvips/foreign/dzsave.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 ); From ee2255da00d34075af12f535b6d10f1aca97a082 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 22 Apr 2021 15:27:18 +0100 Subject: [PATCH 21/23] only warn if fontfile load fails since it can fail for things like adding the same font twice from different files thanks molfar see https://github.com/libvips/libvips/issues/2220 --- libvips/create/text.c | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) 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. From 83c4e56cfb915405f9dda0936e2d00ccf38eea26 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 22 Apr 2021 23:02:34 +0100 Subject: [PATCH 22/23] fix a crash found by oss-fuzz --- libvips/foreign/libnsgif/lzw.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libvips/foreign/libnsgif/lzw.c b/libvips/foreign/libnsgif/lzw.c index 4b521b68..dd30d017 100644 --- a/libvips/foreign/libnsgif/lzw.c +++ b/libvips/foreign/libnsgif/lzw.c @@ -347,8 +347,12 @@ static inline lzw_result lzw__decode(struct lzw_ctx *ctx, } 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) { + if (ctx->prev_code == ctx->clear_code) { + if (code > ctx->clear_code) { + return LZW_BAD_ICODE; + } + + } 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 : From cc1020ae55ee28090f12a5f70a8b7de49d22c7d4 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 24 Apr 2021 18:57:20 +0100 Subject: [PATCH 23/23] update libnsgif for improved lzw decode --- libvips/foreign/libnsgif/libnsgif.c | 1953 ++++++++--------- libvips/foreign/libnsgif/lzw.c | 97 +- libvips/foreign/libnsgif/lzw.h | 17 +- .../libnsgif/patches/no-input-modify.patch | 47 +- 4 files changed, 1058 insertions(+), 1056 deletions(-) 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 dd30d017..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,31 +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) { - if (code > ctx->clear_code) { - return LZW_BAD_ICODE; - } - - } 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; - } + 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; @@ -432,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;