From c7f69718da7f4d1090f4af73906538d33f288b56 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 1 Apr 2021 09:21:41 +0100 Subject: [PATCH 01/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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 );