Merge pull request #2181 from libvips/add-jxl

Add JPEG-XL support
This commit is contained in:
John Cupitt 2021-04-25 16:20:57 +01:00 committed by GitHub
commit 9e6389bd38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1868 additions and 10 deletions

View File

@ -22,6 +22,7 @@
- add vips_image_[set|get]_array_double() - add vips_image_[set|get]_array_double()
- add GIF load with libnsgif - add GIF load with libnsgif
- add JPEG2000 load and save - add JPEG2000 load and save
- add JPEG-XL load and save
- add black_point_compensation flag for icc transforms - add black_point_compensation flag for icc transforms
- add "rgba" flag to vips_text() to enable full colour text rendering - add "rgba" flag to vips_text() to enable full colour text rendering

View File

@ -22,10 +22,11 @@ operations, frequency filtering, colour, resampling,
statistics and others. It supports a large range of [numeric statistics and others. It supports a large range of [numeric
types](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsBandFormat), 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. 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, It supports a good range of image formats, including JPEG, JPEG2000, JPEG-XL,
WebP, HEIC, AVIF, FITS, Matlab, OpenEXR, PDF, SVG, HDR, PPM / PGM / PFM, TIFF, PNG, WebP, HEIC, AVIF, FITS, Matlab, OpenEXR, PDF, SVG, HDR, PPM / PGM /
CSV, GIF, Analyze, NIfTI, DeepZoom, and OpenSlide. It can also load images PFM, CSV, GIF, Analyze, NIfTI, DeepZoom, and OpenSlide. It can also load
via ImageMagick or GraphicsMagick, letting it work with formats like DICOM. images via ImageMagick or GraphicsMagick, letting it work with formats
like DICOM.
It comes with bindings for It comes with bindings for
[C](https://libvips.github.io/libvips/API/current/using-from-c.html), [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), [mediawiki](https://www.mediawiki.org/wiki/Extension:VipsScaler),
[PhotoFlow](https://github.com/aferrero2707/PhotoFlow) and others. [PhotoFlow](https://github.com/aferrero2707/PhotoFlow) and others.
The official libvips GUI is [nip2](https://github.com/libvips/nip2), 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 # Install
@ -278,6 +279,10 @@ OpenEXR images.
If available, libvips will read and write JPEG2000 images. If available, libvips will read and write JPEG2000 images.
### libjxl
If available, libvips will read and write JPEG-XL images.
### OpenSlide ### OpenSlide
If available, libvips can load OpenSlide-supported virtual slide If available, libvips can load OpenSlide-supported virtual slide

View File

@ -770,6 +770,28 @@ VIPS_CFLAGS="$VIPS_CFLAGS $NIFTI_CFLAGS"
VIPS_INCLUDES="$VIPS_INCLUDES $NIFTI_INCLUDES" VIPS_INCLUDES="$VIPS_INCLUDES $NIFTI_INCLUDES"
VIPS_LIBS="$VIPS_LIBS $NIFTI_LIBS" VIPS_LIBS="$VIPS_LIBS $NIFTI_LIBS"
# jpeg-xl
AC_ARG_WITH([libjxl],
AS_HELP_STRING([--without-libjxl],
[build without libjxl (default: test)]))
if test x"$with_libjxl" != x"no"; then
PKG_CHECK_MODULES(LIBJXL, libjxl_threads >= 0.3.7 libjxl >= 0.3.7,
[AC_DEFINE(HAVE_LIBJXL,1,
[define if you have libjxl >= 0.3.7 installed.])
with_libjxl=yes
PACKAGES_USED="$PACKAGES_USED libjxl"
],
[AC_MSG_WARN([libjxl not found; disabling libjxl support])
with_libjxl=no
]
)
fi
VIPS_CFLAGS="$VIPS_CFLAGS $LIBJXL_CFLAGS"
VIPS_INCLUDES="$VIPS_INCLUDES $LIBJXL_INCLUDES"
VIPS_LIBS="$VIPS_LIBS $LIBJXL_LIBS"
# openjpeg # openjpeg
AC_ARG_WITH([libopenjp2], AC_ARG_WITH([libopenjp2],
AS_HELP_STRING([--without-libopenjp2], AS_HELP_STRING([--without-libopenjp2],
@ -1454,6 +1476,9 @@ EXIF metadata support with libexif: $with_libexif
## File format support ## File format support
JPEG load/save with libjpeg: $with_jpeg 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 PNG load with libspng: $with_libspng
(requires libspng-0.6 or later) (requires libspng-0.6 or later)
PNG load/save with libpng: $with_png PNG load/save with libpng: $with_png
@ -1472,8 +1497,6 @@ PDF load with poppler-glib: $with_poppler
SVG load with librsvg-2.0: $with_rsvg SVG load with librsvg-2.0: $with_rsvg
(requires librsvg-2.0 2.34.0 or later) (requires librsvg-2.0 2.34.0 or later)
EXR load with OpenEXR: $with_OpenEXR 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 slide load with OpenSlide: $with_openslide
(requires openslide-3.3.0 or later) (requires openslide-3.3.0 or later)
Matlab load with matio: $with_matio Matlab load with matio: $with_matio

View File

@ -19,6 +19,8 @@ libforeign_la_SOURCES = \
foreign.c \ foreign.c \
heifload.c \ heifload.c \
heifsave.c \ heifsave.c \
jxlload.c \
jxlsave.c \
jp2kload.c \ jp2kload.c \
jp2ksave.c \ jp2ksave.c \
jpeg2vips.c \ jpeg2vips.c \

View File

@ -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 /* 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. * to RGB.
*/ */
if( !coding[VIPS_CODING_RAD] && 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_CMYK &&
in->Type != VIPS_INTERPRETATION_sRGB && in->Type != VIPS_INTERPRETATION_sRGB &&
in->Type != VIPS_INTERPRETATION_RGB16 && in->Type != VIPS_INTERPRETATION_RGB16 &&
in->Type != VIPS_INTERPRETATION_scRGB &&
vips_colourspace_issupported( in ) && vips_colourspace_issupported( in ) &&
(saveable == VIPS_SAVEABLE_RGB || (saveable == VIPS_SAVEABLE_RGB ||
saveable == VIPS_SAVEABLE_RGBA || saveable == VIPS_SAVEABLE_RGBA ||
@ -2183,6 +2184,13 @@ vips_foreign_operation_init( void )
extern GType vips_foreign_save_jp2k_buffer_get_type( void ); extern GType vips_foreign_save_jp2k_buffer_get_type( void );
extern GType vips_foreign_save_jp2k_target_get_type( void ); extern GType vips_foreign_save_jp2k_target_get_type( void );
extern GType vips_foreign_load_jxl_file_get_type( void );
extern GType vips_foreign_load_jxl_buffer_get_type( void );
extern GType vips_foreign_load_jxl_source_get_type( void );
extern GType vips_foreign_save_jxl_file_get_type( void );
extern GType vips_foreign_save_jxl_buffer_get_type( void );
extern GType vips_foreign_save_jxl_target_get_type( void );
extern GType vips_foreign_load_heif_file_get_type( void ); extern GType vips_foreign_load_heif_file_get_type( void );
extern GType vips_foreign_load_heif_buffer_get_type( void ); extern GType vips_foreign_load_heif_buffer_get_type( void );
extern GType vips_foreign_load_heif_source_get_type( void ); extern GType vips_foreign_load_heif_source_get_type( void );
@ -2256,6 +2264,15 @@ vips_foreign_operation_init( void )
vips_foreign_load_svg_source_get_type(); vips_foreign_load_svg_source_get_type();
#endif /*HAVE_RSVG*/ #endif /*HAVE_RSVG*/
#ifdef HAVE_LIBJXL
vips_foreign_load_jxl_file_get_type();
vips_foreign_load_jxl_buffer_get_type();
vips_foreign_load_jxl_source_get_type();
vips_foreign_save_jxl_file_get_type();
vips_foreign_save_jxl_buffer_get_type();
vips_foreign_save_jxl_target_get_type();
#endif /*HAVE_LIBJXL*/
#ifdef HAVE_LIBOPENJP2 #ifdef HAVE_LIBOPENJP2
vips_foreign_load_jp2k_file_get_type(); vips_foreign_load_jp2k_file_get_type();
vips_foreign_load_jp2k_buffer_get_type(); vips_foreign_load_jp2k_buffer_get_type();

View File

@ -274,6 +274,10 @@ vips_foreign_load_jp2k_error_callback( const char *msg, void *client )
vips_error( class->nickname, "%s", msg ); vips_error( class->nickname, "%s", msg );
jp2k->n_errors += 1; jp2k->n_errors += 1;
#ifdef DEBUG
printf( "%s: error %s", class->nickname, msg );
#endif /*DEBUG*/
} }
/* The openjpeg info and warning callbacks are incredibly chatty. /* 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], if( vips_tilecache( t[0], &t[1],
"tile_width", tile_width, "tile_width", tile_width,
"tile_height", tile_height, "tile_height", tile_height,
"max_tiles", (int) (2.5 * tiles_across), "max_tiles", 3 * tiles_across,
NULL ) ) NULL ) )
return( -1 ); return( -1 );
if( vips_image_write( t[1], load->real ) ) if( vips_image_write( t[1], load->real ) )

View File

@ -1113,7 +1113,7 @@ vips_jp2ksave( VipsImage *in, const char *filename, ... )
* * @tile_height: %gint for tile size * * @tile_height: %gint for tile size
* * @subsample_mode: #VipsForeignSubsample, chroma subsampling mode * * @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(). * See also: vips_jp2ksave(), vips_image_write_to_target().
* *

973
libvips/foreign/jxlload.c Normal file
View File

@ -0,0 +1,973 @@
/* load jpeg-xl
*
* 18/3/20
* - from heifload.c
*/
/*
This file is part of VIPS.
VIPS is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
/*
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
*/
/*
#define DEBUG_VERBOSE
#define DEBUG
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vips/vips.h>
#include <vips/debug.h>
#include <vips/internal.h>
#ifdef HAVE_LIBJXL
#include <jxl/decode.h>
#include <jxl/thread_parallel_runner.h>
#include "pforeign.h"
/* TODO:
*
* - add metadata support
*
* - add animation support
*
* - add "shrink" option to read out 8x shrunk image?
*
* - fix scRGB gamma
*/
#define INPUT_BUFFER_SIZE (4096)
typedef struct _VipsForeignLoadJxl {
VipsForeignLoad parent_object;
/* Source to load from (set by subclasses).
*/
VipsSource *source;
/* Page set by user, then we translate that into shrink factor.
*/
int page;
int shrink;
/* Base image properties.
*/
JxlBasicInfo info;
JxlPixelFormat format;
size_t icc_size;
uint8_t *icc_data;
/* Decompress state.
*/
void *runner;
JxlDecoder *decoder;
/* Our input buffer.
*/
uint8_t input_buffer[INPUT_BUFFER_SIZE];
size_t bytes_in_buffer;
/* Number of errors reported during load -- use this to block load of
* corrupted images.
*/
int n_errors;
/* If we need to upsample tiles read from opj.
*/
gboolean upsample;
/* If we need to do ycc->rgb conversion on load.
*/
gboolean ycc_to_rgb;
} VipsForeignLoadJxl;
typedef VipsForeignLoadClass VipsForeignLoadJxlClass;
G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadJxl, vips_foreign_load_jxl,
VIPS_TYPE_FOREIGN_LOAD );
static void
vips_foreign_load_jxl_dispose( GObject *gobject )
{
VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) gobject;
#ifdef DEBUG
printf( "vips_foreign_load_jxl_dispose:\n" );
#endif /*DEBUG*/
VIPS_FREEF( JxlThreadParallelRunnerDestroy, jxl->runner );
VIPS_FREEF( JxlDecoderDestroy, jxl->decoder );
VIPS_FREE( jxl->icc_data );
G_OBJECT_CLASS( vips_foreign_load_jxl_parent_class )->
dispose( gobject );
}
static void
vips_foreign_load_jxl_error( VipsForeignLoadJxl *jxl, const char *details )
{
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( jxl );
/* TODO ... jxl has no way to get error messages at the moment.
*/
vips_error( class->nickname, "error %s", details );
}
static int
vips_foreign_load_jxl_build( VipsObject *object )
{
VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) object;
#ifdef DEBUG
printf( "vips_foreign_load_jxl_build:\n" );
#endif /*DEBUG*/
jxl->runner = JxlThreadParallelRunnerCreate( NULL,
vips_concurrency_get() );
jxl->decoder = JxlDecoderCreate( NULL );
if( JxlDecoderSubscribeEvents( jxl->decoder,
JXL_DEC_COLOR_ENCODING |
JXL_DEC_BASIC_INFO |
JXL_DEC_FULL_IMAGE ) ) {
vips_foreign_load_jxl_error( jxl, "JxlDecoderSubscribeEvents" );
return( -1 );
}
if( JxlDecoderSetParallelRunner( jxl->decoder,
JxlThreadParallelRunner, jxl->runner ) ) {
vips_foreign_load_jxl_error( jxl,
"JxlDecoderSetParallelRunner" );
return( -1 );
}
if( VIPS_OBJECT_CLASS( vips_foreign_load_jxl_parent_class )->
build( object ) )
return( -1 );
return( 0 );
}
static gboolean
vips_foreign_load_jxl_is_a_source( VipsSource *source )
{
const unsigned char *p;
JxlSignature sig;
return( (p = vips_source_sniff( source, 12 )) &&
(sig = JxlSignatureCheck( p, 12 )) == JXL_SIG_CODESTREAM );
}
static VipsForeignFlags
vips_foreign_load_jxl_get_flags( VipsForeignLoad *load )
{
return( VIPS_FOREIGN_PARTIAL );
}
static int
vips_foreign_load_jxl_fill_input( VipsForeignLoadJxl *jxl,
size_t bytes_remaining )
{
gint64 bytes_read;
memcpy( jxl->input_buffer,
jxl->input_buffer + jxl->bytes_in_buffer - bytes_remaining,
bytes_remaining );
bytes_read = vips_source_read( jxl->source,
jxl->input_buffer + bytes_remaining,
INPUT_BUFFER_SIZE - bytes_remaining );
if( bytes_read < 0 )
return( -1 );
jxl->bytes_in_buffer = bytes_read + bytes_remaining;
return( 0 );
}
#ifdef DEBUG
static void
vips_foreign_load_jxl_print_status( JxlDecoderStatus status )
{
switch( status ) {
case JXL_DEC_SUCCESS:
printf( "JXL_DEC_SUCCESS\n" );
break;
case JXL_DEC_ERROR:
printf( "JXL_DEC_ERROR\n" );
break;
case JXL_DEC_NEED_MORE_INPUT:
printf( "JXL_DEC_NEED_MORE_INPUT\n" );
break;
case JXL_DEC_NEED_PREVIEW_OUT_BUFFER:
printf( "JXL_DEC_NEED_PREVIEW_OUT_BUFFER\n" );
break;
case JXL_DEC_NEED_DC_OUT_BUFFER:
printf( "JXL_DEC_NEED_DC_OUT_BUFFER\n" );
break;
case JXL_DEC_NEED_IMAGE_OUT_BUFFER:
printf( "JXL_DEC_NEED_IMAGE_OUT_BUFFER\n" );
break;
case JXL_DEC_JPEG_NEED_MORE_OUTPUT:
printf( "JXL_DEC_JPEG_NEED_MORE_OUTPUT\n" );
break;
case JXL_DEC_BASIC_INFO:
printf( "JXL_DEC_BASIC_INFO\n" );
break;
case JXL_DEC_EXTENSIONS:
printf( "JXL_DEC_EXTENSIONS\n" );
break;
case JXL_DEC_COLOR_ENCODING:
printf( "JXL_DEC_COLOR_ENCODING\n" );
break;
case JXL_DEC_PREVIEW_IMAGE:
printf( "JXL_DEC_PREVIEW_IMAGE\n" );
break;
case JXL_DEC_FRAME:
printf( "JXL_DEC_FRAME\n" );
break;
case JXL_DEC_DC_IMAGE:
printf( "JXL_DEC_DC_IMAGE\n" );
break;
case JXL_DEC_FULL_IMAGE:
printf( "JXL_DEC_FULL_IMAGE\n" );
break;
case JXL_DEC_JPEG_RECONSTRUCTION:
printf( "JXL_DEC_JPEG_RECONSTRUCTION\n" );
break;
default:
printf( "JXL_DEC_<unknown>\n" );
break;
}
}
static void
vips_foreign_load_jxl_print_info( JxlBasicInfo *info )
{
printf( "JxlBasicInfo:\n" );
printf( " have_container = %d\n", info->have_container );
printf( " xsize = %d\n", info->xsize );
printf( " ysize = %d\n", info->ysize );
printf( " bits_per_sample = %d\n", info->bits_per_sample );
printf( " exponent_bits_per_sample = %d\n",
info->exponent_bits_per_sample );
printf( " intensity_target = %g\n", info->intensity_target );
printf( " min_nits = %g\n", info->min_nits );
printf( " relative_to_max_display = %d\n",
info->relative_to_max_display );
printf( " linear_below = %g\n", info->linear_below );
printf( " uses_original_profile = %d\n",
info->uses_original_profile );
printf( " have_preview = %d\n", info->have_preview );
printf( " have_animation = %d\n", info->have_animation );
printf( " orientation = %d\n", info->orientation );
printf( " num_color_channels = %d\n", info->num_color_channels );
printf( " num_extra_channels = %d\n", info->num_extra_channels );
printf( " alpha_bits = %d\n", info->alpha_bits );
printf( " alpha_exponent_bits = %d\n", info->alpha_exponent_bits );
printf( " alpha_premultiplied = %d\n", info->alpha_premultiplied );
printf( " preview.xsize = %d\n", info->preview.xsize );
printf( " preview.ysize = %d\n", info->preview.ysize );
printf( " animation.tps_numerator = %d\n",
info->animation.tps_numerator );
printf( " animation.tps_denominator = %d\n",
info->animation.tps_denominator );
printf( " animation.num_loops = %d\n", info->animation.num_loops );
printf( " animation.have_timecodes = %d\n",
info->animation.have_timecodes );
}
static void
vips_foreign_load_jxl_print_format( JxlPixelFormat *format )
{
printf( "JxlPixelFormat:\n" );
printf( " data_type = " );
switch( format->data_type ) {
case JXL_TYPE_UINT8:
printf( "JXL_TYPE_UINT8" );
break;
case JXL_TYPE_UINT16:
printf( "JXL_TYPE_UINT16" );
break;
case JXL_TYPE_UINT32:
printf( "JXL_TYPE_UINT32" );
break;
case JXL_TYPE_FLOAT:
printf( "JXL_TYPE_FLOAT" );
break;
default:
printf( "(unknown)" );
break;
}
printf( "\n" );
printf( " num_channels = %d\n", format->num_channels );
printf( " endianness = %d\n", format->endianness );
printf( " align = %zd\n", format->align );
}
#endif /*DEBUG*/
static JxlDecoderStatus
vips_foreign_load_jxl_process( VipsForeignLoadJxl *jxl )
{
JxlDecoderStatus status;
while( (status = JxlDecoderProcessInput( jxl->decoder )) ==
JXL_DEC_NEED_MORE_INPUT ) {
size_t bytes_remaining;
bytes_remaining = JxlDecoderReleaseInput( jxl->decoder );
if( vips_foreign_load_jxl_fill_input( jxl, bytes_remaining ) &&
bytes_remaining == 0 )
return( JXL_DEC_ERROR );
JxlDecoderSetInput( jxl->decoder,
jxl->input_buffer, jxl->bytes_in_buffer );
}
#ifdef DEBUG
printf( "vips_foreign_load_jxl_process: seen " );
vips_foreign_load_jxl_print_status( status );
#endif /*DEBUG*/
return( status );
}
static int
vips_foreign_load_jxl_set_header( VipsForeignLoadJxl *jxl, VipsImage *out )
{
VipsBandFormat format;
VipsInterpretation interpretation;
switch( jxl->format.data_type ) {
case JXL_TYPE_UINT8:
format = VIPS_FORMAT_UCHAR;
break;
case JXL_TYPE_UINT16:
format = VIPS_FORMAT_USHORT;
break;
case JXL_TYPE_UINT32:
format = VIPS_FORMAT_UINT;
break;
case JXL_TYPE_FLOAT:
format = VIPS_FORMAT_FLOAT;
break;
default:
g_assert_not_reached();
}
switch( jxl->info.num_color_channels ) {
case 1:
switch( format ) {
case VIPS_FORMAT_UCHAR:
interpretation = VIPS_INTERPRETATION_B_W;
break;
case VIPS_FORMAT_USHORT:
case VIPS_FORMAT_UINT:
interpretation = VIPS_INTERPRETATION_GREY16;
break;
default:
interpretation = VIPS_INTERPRETATION_B_W;
break;
}
break;
case 3:
switch( format ) {
case VIPS_FORMAT_UCHAR:
interpretation = VIPS_INTERPRETATION_sRGB;
break;
case VIPS_FORMAT_USHORT:
case VIPS_FORMAT_UINT:
interpretation = VIPS_INTERPRETATION_RGB16;
break;
case VIPS_FORMAT_FLOAT:
interpretation = VIPS_INTERPRETATION_scRGB;
break;
default:
interpretation = VIPS_INTERPRETATION_sRGB;
break;
}
break;
default:
interpretation = VIPS_INTERPRETATION_MULTIBAND;
break;
}
/* Even though this is a full image reader, we hint thinstrip since
* we are quite happy serving that if anything downstream
* would like it.
*/
vips_image_pipelinev( out, VIPS_DEMAND_STYLE_THINSTRIP, NULL );
vips_image_init_fields( out,
jxl->info.xsize, jxl->info.ysize, jxl->format.num_channels,
format, VIPS_CODING_NONE, interpretation, 1.0, 1.0 );
if( jxl->icc_data &&
jxl->icc_size > 0 ) {
vips_image_set_blob( out, VIPS_META_ICC_NAME,
(VipsCallbackFn) vips_area_free_cb,
jxl->icc_data, jxl->icc_size );
jxl->icc_data = NULL;
jxl->icc_size = 0;
}
vips_image_set_int( out,
VIPS_META_ORIENTATION, jxl->info.orientation );
return( 0 );
}
static int
vips_foreign_load_jxl_header( VipsForeignLoad *load )
{
VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) load;
JxlDecoderStatus status;
#ifdef DEBUG
printf( "vips_foreign_load_jxl_header:\n" );
#endif /*DEBUG*/
if( vips_foreign_load_jxl_fill_input( jxl, 0 ) )
return( -1 );
JxlDecoderSetInput( jxl->decoder,
jxl->input_buffer, jxl->bytes_in_buffer );
/* Read to the end of the header.
*/
do {
switch( (status = vips_foreign_load_jxl_process( jxl )) ) {
case JXL_DEC_ERROR:
vips_foreign_load_jxl_error( jxl,
"JxlDecoderProcessInput" );
return( -1 );
case JXL_DEC_BASIC_INFO:
if( JxlDecoderGetBasicInfo( jxl->decoder,
&jxl->info ) ) {
vips_foreign_load_jxl_error( jxl,
"JxlDecoderGetBasicInfo" );
return( -1 );
}
#ifdef DEBUG
vips_foreign_load_jxl_print_info( &jxl->info );
#endif /*DEBUG*/
/* Pick a pixel format to decode to.
*/
jxl->format.num_channels =
jxl->info.num_color_channels +
jxl->info.num_extra_channels;
if( jxl->info.exponent_bits_per_sample > 0 ||
jxl->info.alpha_exponent_bits > 0 )
jxl->format.data_type = JXL_TYPE_FLOAT;
else if( jxl->info.bits_per_sample > 16 )
jxl->format.data_type = JXL_TYPE_UINT32;
else if( jxl->info.bits_per_sample > 8 )
jxl->format.data_type = JXL_TYPE_UINT16;
else
jxl->format.data_type = JXL_TYPE_UINT8;
jxl->format.endianness = JXL_NATIVE_ENDIAN;
jxl->format.align = 0;
#ifdef DEBUG
vips_foreign_load_jxl_print_format( &jxl->format );
#endif /*DEBUG*/
break;
case JXL_DEC_COLOR_ENCODING:
if( JxlDecoderGetICCProfileSize( jxl->decoder,
&jxl->format,
JXL_COLOR_PROFILE_TARGET_DATA,
&jxl->icc_size ) ) {
vips_foreign_load_jxl_error( jxl,
"JxlDecoderGetICCProfileSize" );
return( -1 );
}
if( !(jxl->icc_data = vips_malloc( NULL,
jxl->icc_size )) )
return( -1 );
if( JxlDecoderGetColorAsICCProfile( jxl->decoder,
&jxl->format,
JXL_COLOR_PROFILE_TARGET_DATA,
jxl->icc_data, jxl->icc_size ) ) {
vips_foreign_load_jxl_error( jxl,
"JxlDecoderGetColorAsICCProfile" );
return( -1 );
}
break;
default:
break;
}
} while( status != JXL_DEC_NEED_IMAGE_OUT_BUFFER );
if( vips_foreign_load_jxl_set_header( jxl, load->out ) )
return( -1 );
VIPS_SETSTR( load->out->filename,
vips_connection_filename( VIPS_CONNECTION( jxl->source ) ) );
return( 0 );
}
static int
vips_foreign_load_jxl_load( VipsForeignLoad *load )
{
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load );
VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) load;
VipsImage **t = (VipsImage **)
vips_object_local_array( VIPS_OBJECT( load ), 3 );
size_t buffer_size;
JxlDecoderStatus status;
#ifdef DEBUG
printf( "vips_foreign_load_jxl_load:\n" );
#endif /*DEBUG*/
t[0] = vips_image_new();
if( vips_foreign_load_jxl_set_header( jxl, t[0] ) )
return( -1 );
/* Read to the end of the image.
*/
do {
switch( (status = vips_foreign_load_jxl_process( jxl )) ) {
case JXL_DEC_ERROR:
vips_foreign_load_jxl_error( jxl,
"JxlDecoderProcessInput" );
return( -1 );
case JXL_DEC_NEED_IMAGE_OUT_BUFFER:
if( vips_image_write_prepare( t[0] ) )
return( -1 );
if( JxlDecoderImageOutBufferSize( jxl->decoder,
&jxl->format,
&buffer_size ) ) {
vips_foreign_load_jxl_error( jxl,
"JxlDecoderImageOutBufferSize" );
return( -1 );
}
if( buffer_size !=
VIPS_IMAGE_SIZEOF_IMAGE( t[0] ) ) {
vips_error( class->nickname,
"%s", _( "bad buffer size" ) );
return( -1 );
}
if( JxlDecoderSetImageOutBuffer( jxl->decoder,
&jxl->format,
VIPS_IMAGE_ADDR( t[0], 0, 0 ),
VIPS_IMAGE_SIZEOF_IMAGE( t[0] ) ) ) {
vips_foreign_load_jxl_error( jxl,
"JxlDecoderSetImageOutBuffer" );
return( -1 );
}
break;
case JXL_DEC_FULL_IMAGE:
/* Image decoded.
*/
break;
default:
break;
}
} while( status != JXL_DEC_SUCCESS );
if( vips_image_write( t[0], load->real ) )
return( -1 );
return( 0 );
}
static void
vips_foreign_load_jxl_class_init( VipsForeignLoadJxlClass *class )
{
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
VipsObjectClass *object_class = (VipsObjectClass *) class;
VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
gobject_class->dispose = vips_foreign_load_jxl_dispose;
gobject_class->set_property = vips_object_set_property;
gobject_class->get_property = vips_object_get_property;
object_class->nickname = "jxlload_base";
object_class->description = _( "load JPEG-XL image" );
object_class->build = vips_foreign_load_jxl_build;
load_class->get_flags = vips_foreign_load_jxl_get_flags;
load_class->header = vips_foreign_load_jxl_header;
load_class->load = vips_foreign_load_jxl_load;
}
static void
vips_foreign_load_jxl_init( VipsForeignLoadJxl *jxl )
{
}
typedef struct _VipsForeignLoadJxlFile {
VipsForeignLoadJxl parent_object;
/* Filename for load.
*/
char *filename;
} VipsForeignLoadJxlFile;
typedef VipsForeignLoadJxlClass VipsForeignLoadJxlFileClass;
G_DEFINE_TYPE( VipsForeignLoadJxlFile, vips_foreign_load_jxl_file,
vips_foreign_load_jxl_get_type() );
static int
vips_foreign_load_jxl_file_build( VipsObject *object )
{
VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) object;
VipsForeignLoadJxlFile *file = (VipsForeignLoadJxlFile *) object;
if( file->filename &&
!(jxl->source = vips_source_new_from_file( file->filename )) )
return( -1 );
if( VIPS_OBJECT_CLASS( vips_foreign_load_jxl_file_parent_class )->
build( object ) )
return( -1 );
return( 0 );
}
const char *vips__jxl_suffs[] =
{ ".jxl", NULL };
static int
vips_foreign_load_jxl_is_a( const char *filename )
{
VipsSource *source;
gboolean result;
if( !(source = vips_source_new_from_file( filename )) )
return( FALSE );
result = vips_foreign_load_jxl_is_a_source( source );
VIPS_UNREF( source );
return( result );
}
static void
vips_foreign_load_jxl_file_class_init( VipsForeignLoadJxlFileClass *class )
{
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
VipsObjectClass *object_class = (VipsObjectClass *) class;
VipsForeignClass *foreign_class = (VipsForeignClass *) class;
VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
gobject_class->set_property = vips_object_set_property;
gobject_class->get_property = vips_object_get_property;
object_class->nickname = "jxlload";
object_class->build = vips_foreign_load_jxl_file_build;
foreign_class->suffs = vips__jxl_suffs;
load_class->is_a = vips_foreign_load_jxl_is_a;
VIPS_ARG_STRING( class, "filename", 1,
_( "Filename" ),
_( "Filename to load from" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsForeignLoadJxlFile, filename ),
NULL );
}
static void
vips_foreign_load_jxl_file_init( VipsForeignLoadJxlFile *jxl )
{
}
typedef struct _VipsForeignLoadJxlBuffer {
VipsForeignLoadJxl parent_object;
/* Load from a buffer.
*/
VipsArea *buf;
} VipsForeignLoadJxlBuffer;
typedef VipsForeignLoadJxlClass VipsForeignLoadJxlBufferClass;
G_DEFINE_TYPE( VipsForeignLoadJxlBuffer, vips_foreign_load_jxl_buffer,
vips_foreign_load_jxl_get_type() );
static int
vips_foreign_load_jxl_buffer_build( VipsObject *object )
{
VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) object;
VipsForeignLoadJxlBuffer *buffer =
(VipsForeignLoadJxlBuffer *) object;
if( buffer->buf )
if( !(jxl->source = vips_source_new_from_memory(
VIPS_AREA( buffer->buf )->data,
VIPS_AREA( buffer->buf )->length )) )
return( -1 );
if( VIPS_OBJECT_CLASS( vips_foreign_load_jxl_file_parent_class )->
build( object ) )
return( -1 );
return( 0 );
}
static gboolean
vips_foreign_load_jxl_buffer_is_a( const void *buf, size_t len )
{
VipsSource *source;
gboolean result;
if( !(source = vips_source_new_from_memory( buf, len )) )
return( FALSE );
result = vips_foreign_load_jxl_is_a_source( source );
VIPS_UNREF( source );
return( result );
}
static void
vips_foreign_load_jxl_buffer_class_init( VipsForeignLoadJxlBufferClass *class )
{
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
VipsObjectClass *object_class = (VipsObjectClass *) class;
VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
gobject_class->set_property = vips_object_set_property;
gobject_class->get_property = vips_object_get_property;
object_class->nickname = "jxlload_buffer";
object_class->build = vips_foreign_load_jxl_buffer_build;
load_class->is_a_buffer = vips_foreign_load_jxl_buffer_is_a;
VIPS_ARG_BOXED( class, "buffer", 1,
_( "Buffer" ),
_( "Buffer to load from" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsForeignLoadJxlBuffer, buf ),
VIPS_TYPE_BLOB );
}
static void
vips_foreign_load_jxl_buffer_init( VipsForeignLoadJxlBuffer *buffer )
{
}
typedef struct _VipsForeignLoadJxlSource {
VipsForeignLoadJxl parent_object;
/* Load from a source.
*/
VipsSource *source;
} VipsForeignLoadJxlSource;
typedef VipsForeignLoadJxlClass VipsForeignLoadJxlSourceClass;
G_DEFINE_TYPE( VipsForeignLoadJxlSource, vips_foreign_load_jxl_source,
vips_foreign_load_jxl_get_type() );
static int
vips_foreign_load_jxl_source_build( VipsObject *object )
{
VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) object;
VipsForeignLoadJxlSource *source =
(VipsForeignLoadJxlSource *) object;
if( source->source ) {
jxl->source = source->source;
g_object_ref( jxl->source );
}
if( VIPS_OBJECT_CLASS(
vips_foreign_load_jxl_source_parent_class )->
build( object ) )
return( -1 );
return( 0 );
}
static void
vips_foreign_load_jxl_source_class_init( VipsForeignLoadJxlSourceClass *class )
{
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
VipsObjectClass *object_class = (VipsObjectClass *) class;
VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
gobject_class->set_property = vips_object_set_property;
gobject_class->get_property = vips_object_get_property;
object_class->nickname = "jxlload_source";
object_class->build = vips_foreign_load_jxl_source_build;
load_class->is_a_source = vips_foreign_load_jxl_is_a_source;
VIPS_ARG_OBJECT( class, "source", 1,
_( "Source" ),
_( "Source to load from" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsForeignLoadJxlSource, source ),
VIPS_TYPE_SOURCE );
}
static void
vips_foreign_load_jxl_source_init(
VipsForeignLoadJxlSource *jxl )
{
}
#endif /*HAVE_LIBJXL*/
/**
* vips_jxlload:
* @filename: file to load
* @out: (out): decompressed image
* @...: %NULL-terminated list of optional named arguments
*
* Read a JPEG-XL image.
*
* The JPEG-XL loader and saver are experimental features and may change
* in future libvips versions.
*
* See also: vips_image_new_from_file().
*
* Returns: 0 on success, -1 on error.
*/
int
vips_jxlload( const char *filename, VipsImage **out, ... )
{
va_list ap;
int result;
va_start( ap, out );
result = vips_call_split( "jxlload", ap, filename, out );
va_end( ap );
return( result );
}
/**
* vips_jxlload_buffer:
* @buf: (array length=len) (element-type guint8): memory area to load
* @len: (type gsize): size of memory area
* @out: (out): image to write
* @...: %NULL-terminated list of optional named arguments
*
* Exactly as vips_jxlload(), but read from a buffer.
*
* Returns: 0 on success, -1 on error.
*/
int
vips_jxlload_buffer( void *buf, size_t len, VipsImage **out, ... )
{
va_list ap;
VipsBlob *blob;
int result;
/* We don't take a copy of the data or free it.
*/
blob = vips_blob_new( NULL, buf, len );
va_start( ap, out );
result = vips_call_split( "jxlload_buffer", ap, blob, out );
va_end( ap );
vips_area_unref( VIPS_AREA( blob ) );
return( result );
}
/**
* vips_jxlload_source:
* @source: source to load from
* @out: (out): decompressed image
* @...: %NULL-terminated list of optional named arguments
*
* Exactly as vips_jxlload(), but read from a source.
*
* Returns: 0 on success, -1 on error.
*/
int
vips_jxlload_source( VipsSource *source, VipsImage **out, ... )
{
va_list ap;
int result;
va_start( ap, out );
result = vips_call_split( "jxlload_source", ap, source, out );
va_end( ap );
return( result );
}

823
libvips/foreign/jxlsave.c Normal file
View File

@ -0,0 +1,823 @@
/* save as jpeg-xl
*
* 18/3/20
* - from heifload.c
*/
/*
This file is part of VIPS.
VIPS is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
/*
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
*/
/*
#define DEBUG_VERBOSE
#define DEBUG
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vips/vips.h>
#include <vips/internal.h>
#ifdef HAVE_LIBJXL
#include <jxl/encode.h>
#include <jxl/thread_parallel_runner.h>
#include "pforeign.h"
/* TODO:
*
* - libjxl currently seems to be missing API to attach a profile
*
* - libjxl encode only works in one shot mode, so there's no way to write in
* chunks
*
* - add metadata support EXIF, XMP, etc. api for this is on the way
*
* - add animation support
*
* - libjxl is currently missing error messages (I think)
*
* - fix scRGB gamma
*/
#define OUTPUT_BUFFER_SIZE (4096)
typedef struct _VipsForeignSaveJxl {
VipsForeignSave parent_object;
/* Where to write (set by subclasses).
*/
VipsTarget *target;
/* Encoder options.
*/
int tier;
double distance;
int effort;
gboolean lossless;
int Q;
/* Base image properties.
*/
JxlBasicInfo info;
JxlColorEncoding color_encoding;
JxlPixelFormat format;
/* Encoder state.
*/
void *runner;
JxlEncoder *encoder;
/* Write buffer.
*/
uint8_t output_buffer[OUTPUT_BUFFER_SIZE];
} VipsForeignSaveJxl;
typedef VipsForeignSaveClass VipsForeignSaveJxlClass;
G_DEFINE_ABSTRACT_TYPE( VipsForeignSaveJxl, vips_foreign_save_jxl,
VIPS_TYPE_FOREIGN_SAVE );
static void
vips_foreign_save_jxl_dispose( GObject *gobject )
{
VipsForeignSaveJxl *jxl = (VipsForeignSaveJxl *) gobject;
VIPS_FREEF( JxlThreadParallelRunnerDestroy, jxl->runner );
VIPS_FREEF( JxlEncoderDestroy, jxl->encoder );
G_OBJECT_CLASS( vips_foreign_save_jxl_parent_class )->
dispose( gobject );
}
static void
vips_foreign_save_jxl_error( VipsForeignSaveJxl *jxl, const char *details )
{
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( jxl );
/* TODO ... jxl has no way to get error messages at the moment.
*/
vips_error( class->nickname, "error %s", details );
}
#ifdef DEBUG
static void
vips_foreign_save_jxl_print_info( JxlBasicInfo *info )
{
printf( "JxlBasicInfo:\n" );
printf( " have_container = %d\n", info->have_container );
printf( " xsize = %d\n", info->xsize );
printf( " ysize = %d\n", info->ysize );
printf( " bits_per_sample = %d\n", info->bits_per_sample );
printf( " exponent_bits_per_sample = %d\n",
info->exponent_bits_per_sample );
printf( " intensity_target = %g\n", info->intensity_target );
printf( " min_nits = %g\n", info->min_nits );
printf( " relative_to_max_display = %d\n",
info->relative_to_max_display );
printf( " linear_below = %g\n", info->linear_below );
printf( " uses_original_profile = %d\n",
info->uses_original_profile );
printf( " have_preview = %d\n", info->have_preview );
printf( " have_animation = %d\n", info->have_animation );
printf( " orientation = %d\n", info->orientation );
printf( " num_color_channels = %d\n", info->num_color_channels );
printf( " num_extra_channels = %d\n", info->num_extra_channels );
printf( " alpha_bits = %d\n", info->alpha_bits );
printf( " alpha_exponent_bits = %d\n", info->alpha_exponent_bits );
printf( " alpha_premultiplied = %d\n", info->alpha_premultiplied );
printf( " preview.xsize = %d\n", info->preview.xsize );
printf( " preview.ysize = %d\n", info->preview.ysize );
printf( " animation.tps_numerator = %d\n",
info->animation.tps_numerator );
printf( " animation.tps_denominator = %d\n",
info->animation.tps_denominator );
printf( " animation.num_loops = %d\n", info->animation.num_loops );
printf( " animation.have_timecodes = %d\n",
info->animation.have_timecodes );
}
static void
vips_foreign_save_jxl_print_format( JxlPixelFormat *format )
{
printf( "JxlPixelFormat:\n" );
printf( " data_type = " );
switch( format->data_type ) {
case JXL_TYPE_UINT8:
printf( "JXL_TYPE_UINT8" );
break;
case JXL_TYPE_UINT16:
printf( "JXL_TYPE_UINT16" );
break;
case JXL_TYPE_UINT32:
printf( "JXL_TYPE_UINT32" );
break;
case JXL_TYPE_FLOAT:
printf( "JXL_TYPE_FLOAT" );
break;
default:
printf( "(unknown)" );
break;
}
printf( "\n" );
printf( " num_channels = %d\n", format->num_channels );
printf( " endianness = %d\n", format->endianness );
printf( " align = %zd\n", format->align );
}
static void
vips_foreign_save_jxl_print_status( JxlEncoderStatus status )
{
switch( status ) {
case JXL_ENC_SUCCESS:
printf( "JXL_ENC_SUCCESS\n" );
break;
case JXL_ENC_ERROR:
printf( "JXL_ENC_ERROR\n" );
break;
case JXL_ENC_NEED_MORE_OUTPUT:
printf( "JXL_ENC_NEED_MORE_OUTPUT\n" );
break;
case JXL_ENC_NOT_SUPPORTED:
printf( "JXL_ENC_NOT_SUPPORTED\n" );
break;
default:
printf( "JXL_ENC_<unknown>\n" );
break;
}
}
#endif /*DEBUG*/
static int
vips_foreign_save_jxl_build( VipsObject *object )
{
VipsForeignSave *save = (VipsForeignSave *) object;
VipsForeignSaveJxl *jxl = (VipsForeignSaveJxl *) object;
JxlEncoderOptions *options;
JxlEncoderStatus status;
if( VIPS_OBJECT_CLASS( vips_foreign_save_jxl_parent_class )->
build( object ) )
return( -1 );
/* If Q is set and distance is not, use Q to set a rough distance
* value. Formula stolen from cjxl.c and very roughly approximates
* libjpeg values.
*/
if( !vips_object_argument_isset( object, "distance" ) )
jxl->distance = jxl->Q >= 30 ?
0.1 + (100 - jxl->Q) * 0.09 :
6.4 + pow(2.5, (30 - jxl->Q) / 5.0f) / 6.25f;
/* Distance 0 is lossless. libjxl will fail for lossy distance 0.
*/
if( jxl->distance == 0 )
jxl->lossless = TRUE;
jxl->runner = JxlThreadParallelRunnerCreate( NULL,
vips_concurrency_get() );
jxl->encoder = JxlEncoderCreate( NULL );
if( JxlEncoderSetParallelRunner( jxl->encoder,
JxlThreadParallelRunner, jxl->runner ) ) {
vips_foreign_save_jxl_error( jxl,
"JxlDecoderSetParallelRunner" );
return( -1 );
}
switch( save->ready->BandFmt ) {
case VIPS_FORMAT_UCHAR:
jxl->info.bits_per_sample = 8;
jxl->info.exponent_bits_per_sample = 0;
jxl->format.data_type = JXL_TYPE_UINT8;
break;
case VIPS_FORMAT_USHORT:
jxl->info.bits_per_sample = 16;
jxl->info.exponent_bits_per_sample = 0;
jxl->format.data_type = JXL_TYPE_UINT16;
break;
case VIPS_FORMAT_UINT:
jxl->info.bits_per_sample = 32;
jxl->info.exponent_bits_per_sample = 0;
jxl->format.data_type = JXL_TYPE_UINT32;
break;
case VIPS_FORMAT_FLOAT:
jxl->info.bits_per_sample = 32;
jxl->info.exponent_bits_per_sample = 8;
jxl->format.data_type = JXL_TYPE_FLOAT;
break;
default:
g_assert_not_reached();
break;
}
switch( save->ready->Type ) {
case VIPS_INTERPRETATION_B_W:
case VIPS_INTERPRETATION_GREY16:
jxl->info.num_color_channels = 1;
break;
case VIPS_INTERPRETATION_sRGB:
case VIPS_INTERPRETATION_scRGB:
case VIPS_INTERPRETATION_RGB16:
jxl->info.num_color_channels = 3;
break;
default:
jxl->info.num_color_channels = save->ready->Bands;
}
jxl->info.num_extra_channels = VIPS_MAX( 0,
save->ready->Bands - jxl->info.num_color_channels );
jxl->info.xsize = save->ready->Xsize;
jxl->info.ysize = save->ready->Ysize;
jxl->format.num_channels = save->ready->Bands;
jxl->format.endianness = JXL_NATIVE_ENDIAN;
jxl->format.align = 0;
if( vips_image_hasalpha( save->ready ) ) {
jxl->info.alpha_bits = jxl->info.bits_per_sample;
jxl->info.alpha_exponent_bits =
jxl->info.exponent_bits_per_sample;
}
else {
jxl->info.alpha_exponent_bits = 0;
jxl->info.alpha_bits = 0;
}
if( vips_image_get_typeof( save->ready, "stonits" ) ) {
double stonits;
if( vips_image_get_double( save->ready, "stonits", &stonits ) )
return( -1 );
jxl->info.intensity_target = stonits;
}
/* FIXME libjxl doesn't seem to have this API yet.
*
if( vips_image_get_typeof( save->ready, VIPS_META_ICC_NAME ) ) {
const void *data;
size_t length;
if( vips_image_get_blob( save->ready,
VIPS_META_ICC_NAME, &data, &length ) )
return( -1 );
jxl->info.uses_original_profile = JXL_TRUE;
... attach profile
}
else
jxl->info.uses_original_profile = JXL_FALSE;
*/
/* Remove this when libjxl gets API to attach an ICC profile.
*/
jxl->info.uses_original_profile = JXL_FALSE;
if( JxlEncoderSetBasicInfo( jxl->encoder, &jxl->info ) ) {
vips_foreign_save_jxl_error( jxl, "JxlEncoderSetBasicInfo" );
return( -1 );
}
JxlColorEncodingSetToSRGB( &jxl->color_encoding,
jxl->format.num_channels < 3 );
if( JxlEncoderSetColorEncoding( jxl->encoder, &jxl->color_encoding ) ) {
vips_foreign_save_jxl_error( jxl,
"JxlEncoderSetColorEncoding" );
return( -1 );
}
/* Render the entire image in memory. libjxl seems to be missing
* tile-based write at the moment.
*/
if( vips_image_wio_input( save->ready ) )
return( -1 );
options = JxlEncoderOptionsCreate( jxl->encoder, NULL );
JxlEncoderOptionsSetDecodingSpeed( options, jxl->tier );
JxlEncoderOptionsSetDistance( options, jxl->distance );
JxlEncoderOptionsSetEffort( options, jxl->effort );
JxlEncoderOptionsSetLossless( options, jxl->lossless );
#ifdef DEBUG
vips_foreign_save_jxl_print_info( &jxl->info );
vips_foreign_save_jxl_print_format( &jxl->format );
printf( "JxlEncoderOptions:\n" );
printf( " tier = %d\n", jxl->tier );
printf( " distance = %g\n", jxl->distance );
printf( " effort = %d\n", jxl->effort );
printf( " lossless = %d\n", jxl->lossless );
#endif /*DEBUG*/
if( JxlEncoderAddImageFrame( options, &jxl->format,
VIPS_IMAGE_ADDR( save->ready, 0, 0 ),
VIPS_IMAGE_SIZEOF_IMAGE( save->ready ) ) ) {
vips_foreign_save_jxl_error( jxl, "JxlEncoderAddImageFrame" );
return( -1 );
}
do {
uint8_t *out;
size_t avail_out;
out = jxl->output_buffer;
avail_out = OUTPUT_BUFFER_SIZE;
status = JxlEncoderProcessOutput( jxl->encoder,
&out, &avail_out );
switch( status ) {
case JXL_ENC_SUCCESS:
case JXL_ENC_NEED_MORE_OUTPUT:
if( vips_target_write( jxl->target,
jxl->output_buffer,
OUTPUT_BUFFER_SIZE - avail_out ) )
return( -1 );
break;
default:
vips_foreign_save_jxl_error( jxl,
"JxlEncoderProcessOutput" );
#ifdef DEBUG
vips_foreign_save_jxl_print_status( status );
#endif /*DEBUG*/
return( -1 );
}
} while( status != JXL_ENC_SUCCESS );
vips_target_finish( jxl->target );
return( 0 );
}
/* Save a bit of typing.
*/
#define UC VIPS_FORMAT_UCHAR
#define US VIPS_FORMAT_USHORT
#define UI VIPS_FORMAT_UINT
#define F VIPS_FORMAT_FLOAT
/* Type promotion for save ... unsigned ints + float + double.
*/
static int bandfmt_jpeg[10] = {
/* UC C US S UI I F X D DX */
UC, UC, US, US, UI, UI, F, F, F, F
};
static void
vips_foreign_save_jxl_class_init( VipsForeignSaveJxlClass *class )
{
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
VipsObjectClass *object_class = (VipsObjectClass *) class;
VipsForeignClass *foreign_class = (VipsForeignClass *) class;
VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class;
gobject_class->dispose = vips_foreign_save_jxl_dispose;
gobject_class->set_property = vips_object_set_property;
gobject_class->get_property = vips_object_get_property;
object_class->nickname = "jxlsave_base";
object_class->description = _( "save image in JPEG-XL format" );
object_class->build = vips_foreign_save_jxl_build;
foreign_class->suffs = vips__jxl_suffs;
save_class->saveable = VIPS_SAVEABLE_ANY;
save_class->format_table = bandfmt_jpeg;
VIPS_ARG_INT( class, "tier", 10,
_( "Tier" ),
_( "Decode speed tier" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsForeignSaveJxl, tier ),
0, 4, 0 );
VIPS_ARG_DOUBLE( class, "distance", 11,
_( "Distance" ),
_( "Target butteraugli distance" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsForeignSaveJxl, distance ),
0, 15, 1.0 );
VIPS_ARG_INT( class, "effort", 12,
_( "effort" ),
_( "Encoding effort" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsForeignSaveJxl, effort ),
3, 9, 7 );
VIPS_ARG_BOOL( class, "lossless", 13,
_( "Lossless" ),
_( "Enable lossless compression" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsForeignSaveJxl, lossless ),
FALSE );
VIPS_ARG_INT( class, "Q", 14,
_( "Q" ),
_( "Quality factor" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsForeignSaveJxl, Q ),
0, 100, 75 );
}
static void
vips_foreign_save_jxl_init( VipsForeignSaveJxl *jxl )
{
jxl->tier = 0;
jxl->distance = 1.0;
jxl->effort = 7;
jxl->lossless = FALSE;
jxl->Q = 75;
}
typedef struct _VipsForeignSaveJxlFile {
VipsForeignSaveJxl parent_object;
/* Filename for save.
*/
char *filename;
} VipsForeignSaveJxlFile;
typedef VipsForeignSaveJxlClass VipsForeignSaveJxlFileClass;
G_DEFINE_TYPE( VipsForeignSaveJxlFile, vips_foreign_save_jxl_file,
vips_foreign_save_jxl_get_type() );
static int
vips_foreign_save_jxl_file_build( VipsObject *object )
{
VipsForeignSaveJxl *jxl = (VipsForeignSaveJxl *) object;
VipsForeignSaveJxlFile *file = (VipsForeignSaveJxlFile *) object;
if( !(jxl->target = vips_target_new_to_file( file->filename )) )
return( -1 );
if( VIPS_OBJECT_CLASS( vips_foreign_save_jxl_file_parent_class )->
build( object ) )
return( -1 );
return( 0 );
}
static void
vips_foreign_save_jxl_file_class_init( VipsForeignSaveJxlFileClass *class )
{
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
VipsObjectClass *object_class = (VipsObjectClass *) class;
gobject_class->set_property = vips_object_set_property;
gobject_class->get_property = vips_object_get_property;
object_class->nickname = "jxlsave";
object_class->build = vips_foreign_save_jxl_file_build;
VIPS_ARG_STRING( class, "filename", 1,
_( "Filename" ),
_( "Filename to load from" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsForeignSaveJxlFile, filename ),
NULL );
}
static void
vips_foreign_save_jxl_file_init( VipsForeignSaveJxlFile *file )
{
}
typedef struct _VipsForeignSaveJxlBuffer {
VipsForeignSaveJxl parent_object;
/* Save to a buffer.
*/
VipsArea *buf;
} VipsForeignSaveJxlBuffer;
typedef VipsForeignSaveJxlClass VipsForeignSaveJxlBufferClass;
G_DEFINE_TYPE( VipsForeignSaveJxlBuffer, vips_foreign_save_jxl_buffer,
vips_foreign_save_jxl_get_type() );
static int
vips_foreign_save_jxl_buffer_build( VipsObject *object )
{
VipsForeignSaveJxl *jxl = (VipsForeignSaveJxl *) object;
VipsForeignSaveJxlBuffer *buffer =
(VipsForeignSaveJxlBuffer *) object;
VipsBlob *blob;
if( !(jxl->target = vips_target_new_to_memory()) )
return( -1 );
if( VIPS_OBJECT_CLASS( vips_foreign_save_jxl_buffer_parent_class )->
build( object ) )
return( -1 );
g_object_get( jxl->target, "blob", &blob, NULL );
g_object_set( buffer, "buffer", blob, NULL );
vips_area_unref( VIPS_AREA( blob ) );
return( 0 );
}
static void
vips_foreign_save_jxl_buffer_class_init(
VipsForeignSaveJxlBufferClass *class )
{
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
VipsObjectClass *object_class = (VipsObjectClass *) class;
gobject_class->set_property = vips_object_set_property;
gobject_class->get_property = vips_object_get_property;
object_class->nickname = "jxlsave_buffer";
object_class->build = vips_foreign_save_jxl_buffer_build;
VIPS_ARG_BOXED( class, "buffer", 1,
_( "Buffer" ),
_( "Buffer to save to" ),
VIPS_ARGUMENT_REQUIRED_OUTPUT,
G_STRUCT_OFFSET( VipsForeignSaveJxlBuffer, buf ),
VIPS_TYPE_BLOB );
}
static void
vips_foreign_save_jxl_buffer_init( VipsForeignSaveJxlBuffer *buffer )
{
}
typedef struct _VipsForeignSaveJxlTarget {
VipsForeignSaveJxl parent_object;
VipsTarget *target;
} VipsForeignSaveJxlTarget;
typedef VipsForeignSaveJxlClass VipsForeignSaveJxlTargetClass;
G_DEFINE_TYPE( VipsForeignSaveJxlTarget, vips_foreign_save_jxl_target,
vips_foreign_save_jxl_get_type() );
static int
vips_foreign_save_jxl_target_build( VipsObject *object )
{
VipsForeignSaveJxl *jxl = (VipsForeignSaveJxl *) object;
VipsForeignSaveJxlTarget *target =
(VipsForeignSaveJxlTarget *) object;
if( target->target ) {
jxl->target = target->target;
g_object_ref( jxl->target );
}
if( VIPS_OBJECT_CLASS( vips_foreign_save_jxl_target_parent_class )->
build( object ) )
return( -1 );
return( 0 );
}
static void
vips_foreign_save_jxl_target_class_init(
VipsForeignSaveJxlTargetClass *class )
{
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
VipsObjectClass *object_class = (VipsObjectClass *) class;
gobject_class->set_property = vips_object_set_property;
gobject_class->get_property = vips_object_get_property;
object_class->nickname = "jxlsave_target";
object_class->build = vips_foreign_save_jxl_target_build;
VIPS_ARG_OBJECT( class, "target", 1,
_( "Target" ),
_( "Target to save to" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsForeignSaveJxlTarget, target ),
VIPS_TYPE_TARGET );
}
static void
vips_foreign_save_jxl_target_init( VipsForeignSaveJxlTarget *target )
{
}
#endif /*HAVE_LIBOPENJXL*/
/**
* vips_jxlsave: (method)
* @in: image to save
* @filename: file to write to
* @...: %NULL-terminated list of optional named arguments
*
* Optional arguments:
*
* * @tier: %gint, decode speed tier
* * @distance: %gdouble, maximum encoding error
* * @effort: %gint, encoding effort
* * @lossless: %gboolean, enables lossless compression
* * @Q: %gint, quality setting
*
* Write a VIPS image to a file in JPEG-XL format.
*
* The JPEG-XL loader and saver are experimental features and may change
* in future libvips versions.
*
* @tier sets the overall decode speed the encoder will target. Minimum is 0
* (highest quality), and maximum is 4 (lowest quality). Default is 0.
*
* @distance sets the target maximum encoding error. Minimum is 0
* (highest quality), and maximum is 15 (lowest quality). Default is 1.0
* (visually lossless).
*
* As a convenience, you can also use @Q to set @distance. @Q uses
* approximately the same scale as regular JPEG.
*
* Set @lossless to enable lossless compresion.
*
* Returns: 0 on success, -1 on error.
*/
int
vips_jxlsave( VipsImage *in, const char *filename, ... )
{
va_list ap;
int result;
va_start( ap, filename );
result = vips_call_split( "jxlsave", ap, in, filename );
va_end( ap );
return( result );
}
/**
* vips_jxlsave_buffer: (method)
* @in: image to save
* @buf: (array length=len) (element-type guint8): return output buffer here
* @len: (type gsize): return output length here
* @...: %NULL-terminated list of optional named arguments
*
* Optional arguments:
*
* * @tier: %gint, decode speed tier
* * @distance: %gdouble, maximum encoding error
* * @effort: %gint, encoding effort
* * @lossless: %gboolean, enables lossless compression
* * @Q: %gint, quality setting
*
* As vips_jxlsave(), but save to a memory buffer.
*
* See also: vips_jxlsave(), vips_image_write_to_target().
*
* Returns: 0 on success, -1 on error.
*/
int
vips_jxlsave_buffer( VipsImage *in, void **buf, size_t *len, ... )
{
va_list ap;
VipsArea *area;
int result;
area = NULL;
va_start( ap, len );
result = vips_call_split( "jxlsave_buffer", ap, in, &area );
va_end( ap );
if( !result &&
area ) {
if( buf ) {
*buf = area->data;
area->free_fn = NULL;
}
if( len )
*len = area->length;
vips_area_unref( area );
}
return( result );
}
/**
* vips_jxlsave_target: (method)
* @in: image to save
* @target: save image to this target
* @...: %NULL-terminated list of optional named arguments
*
* Optional arguments:
*
* * @tier: %gint, decode speed tier
* * @distance: %gdouble, maximum encoding error
* * @effort: %gint, encoding effort
* * @lossless: %gboolean, enables lossless compression
* * @Q: %gint, quality setting
*
* As vips_jxlsave(), but save to a target.
*
* See also: vips_jxlsave(), vips_image_write_to_target().
*
* Returns: 0 on success, -1 on error.
*/
int
vips_jxlsave_target( VipsImage *in, VipsTarget *target, ... )
{
va_list ap;
int result;
va_start( ap, target );
result = vips_call_split( "jxlsave_target", ap, in, target );
va_end( ap );
return( result );
}

View File

@ -247,6 +247,8 @@ void vips__heif_image_print( struct heif_image *img );
extern const char *vips__jp2k_suffs[]; extern const char *vips__jp2k_suffs[];
extern const char *vips__jxl_suffs[];
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif /*__cplusplus*/ #endif /*__cplusplus*/

View File

@ -327,6 +327,7 @@ vips_foreign_save_ppm_build( VipsObject *object )
*/ */
g_ascii_dtostr( buf, G_ASCII_DTOSTR_BUF_SIZE, scale ); g_ascii_dtostr( buf, G_ASCII_DTOSTR_BUF_SIZE, scale );
vips_target_writes( ppm->target, buf ); vips_target_writes( ppm->target, buf );
vips_target_writes( ppm->target, "\n" );
} }
break; break;

View File

@ -689,6 +689,13 @@ int vips_jp2ksave_buffer( VipsImage *in, void **buf, size_t *len, ... )
int vips_jp2ksave_target( VipsImage *in, VipsTarget *target, ... ) int vips_jp2ksave_target( VipsImage *in, VipsTarget *target, ... )
__attribute__((sentinel)); __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: * VipsForeignDzLayout:
* @VIPS_FOREIGN_DZ_LAYOUT_DZ: use DeepZoom directory layout * @VIPS_FOREIGN_DZ_LAYOUT_DZ: use DeepZoom directory layout