Merge branch 'master' into gmodulized

This commit is contained in:
John Cupitt 2021-04-26 09:03:12 +01:00 committed by GitHub
commit 023f74b037
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 2935 additions and 1073 deletions

View File

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

View File

@ -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

View File

@ -809,6 +809,28 @@ VIPS_CFLAGS="$VIPS_CFLAGS $NIFTI_CFLAGS"
VIPS_INCLUDES="$VIPS_INCLUDES $NIFTI_INCLUDES"
VIPS_LIBS="$VIPS_LIBS $NIFTI_LIBS"
# jpeg-xl
AC_ARG_WITH([libjxl],
AS_HELP_STRING([--without-libjxl],
[build without libjxl (default: test)]))
if test x"$with_libjxl" != x"no"; then
PKG_CHECK_MODULES(LIBJXL, libjxl_threads >= 0.3.7 libjxl >= 0.3.7,
[AC_DEFINE(HAVE_LIBJXL,1,
[define if you have libjxl >= 0.3.7 installed.])
with_libjxl=yes
PACKAGES_USED="$PACKAGES_USED libjxl"
],
[AC_MSG_WARN([libjxl not found; disabling libjxl support])
with_libjxl=no
]
)
fi
VIPS_CFLAGS="$VIPS_CFLAGS $LIBJXL_CFLAGS"
VIPS_INCLUDES="$VIPS_INCLUDES $LIBJXL_INCLUDES"
VIPS_LIBS="$VIPS_LIBS $LIBJXL_LIBS"
# openjpeg
AC_ARG_WITH([libopenjp2],
AS_HELP_STRING([--without-libopenjp2],
@ -1531,6 +1553,9 @@ EXIF metadata support with libexif: $with_libexif
## File format support
JPEG load/save with libjpeg: $with_jpeg
JXL load/save with libjxl: $with_libjxl
JPEG2000 load/save with libopenjp2: $with_libopenjp2
(requires libopenjp2 2.2 or later)
PNG load with libspng: $with_libspng
(requires libspng-0.6 or later)
PNG load/save with libpng: $with_png

View File

@ -126,12 +126,10 @@ static GMutex *vips_text_lock = NULL;
*/
static PangoFontMap *vips_text_fontmap = NULL;
#ifdef HAVE_FONTCONFIG
/* All the fontfiles we've loaded. fontconfig lets you add a fontfile
* repeatedly, and we obviously don't want that.
*/
static GHashTable *vips_text_fontfiles = NULL;
#endif
static void
vips_text_dispose( GObject *gobject )
@ -371,11 +369,9 @@ vips_text_build( VipsObject *object )
if( !vips_text_fontmap )
vips_text_fontmap = pango_cairo_font_map_new();
#ifdef HAVE_FONTCONFIG
if( !vips_text_fontfiles )
vips_text_fontfiles =
g_hash_table_new( g_str_hash, g_str_equal );
#endif
text->context = pango_font_map_create_context(
PANGO_FONT_MAP( vips_text_fontmap ) );
@ -383,23 +379,22 @@ vips_text_build( VipsObject *object )
#ifdef HAVE_FONTCONFIG
if( text->fontfile &&
!g_hash_table_lookup( vips_text_fontfiles, text->fontfile ) ) {
/* This can fail if you eg. add the same font from two
* different files. Just warn.
*/
if( !FcConfigAppFontAddFile( NULL,
(const FcChar8 *) text->fontfile ) ) {
vips_error( class->nickname,
_( "unable to load font \"%s\"" ),
(const FcChar8 *) text->fontfile ) )
g_warning( _( "unable to load fontfile \"%s\"" ),
text->fontfile );
g_mutex_unlock( vips_text_lock );
return( -1 );
}
g_hash_table_insert( vips_text_fontfiles,
text->fontfile,
g_strdup( text->fontfile ) );
}
#else
#else /*!HAVE_FONTCONFIG*/
if( text->fontfile )
g_warning( "%s",
_( "ignoring fontfile (no fontconfig support)" ) );
#endif
#endif /*HAVE_FONTCONFIG*/
/* If our caller set height and not dpi, we adjust dpi until
* we get a fit.

View File

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

View File

@ -366,7 +366,8 @@ vips_gsf_path( VipsGsfDirectory *tree, const char *name, ... )
* path we are creating.
*/
tree->file_count += 1;
tree->filename_lengths += strlen( tree->out->name ) + strlen( name ) + 1;
tree->filename_lengths +=
strlen( tree->out->name ) + strlen( name ) + 1;
dir = tree;
va_start( ap, name );

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
* an RGB image, eg. maybe a LAB or scRGB image, we need to transform
* an RGB image, eg. maybe a LAB image, we need to transform
* to RGB.
*/
if( !coding[VIPS_CODING_RAD] &&
@ -1404,6 +1404,7 @@ vips__foreign_convert_saveable( VipsImage *in, VipsImage **ready,
in->Type != VIPS_INTERPRETATION_CMYK &&
in->Type != VIPS_INTERPRETATION_sRGB &&
in->Type != VIPS_INTERPRETATION_RGB16 &&
in->Type != VIPS_INTERPRETATION_scRGB &&
vips_colourspace_issupported( in ) &&
(saveable == VIPS_SAVEABLE_RGB ||
saveable == VIPS_SAVEABLE_RGBA ||
@ -2183,6 +2184,13 @@ vips_foreign_operation_init( void )
extern GType vips_foreign_save_jp2k_buffer_get_type( void );
extern GType vips_foreign_save_jp2k_target_get_type( void );
extern GType vips_foreign_load_jxl_file_get_type( void );
extern GType vips_foreign_load_jxl_buffer_get_type( void );
extern GType vips_foreign_load_jxl_source_get_type( void );
extern GType vips_foreign_save_jxl_file_get_type( void );
extern GType vips_foreign_save_jxl_buffer_get_type( void );
extern GType vips_foreign_save_jxl_target_get_type( void );
extern GType vips_foreign_load_heif_file_get_type( void );
extern GType vips_foreign_load_heif_buffer_get_type( void );
extern GType vips_foreign_load_heif_source_get_type( void );
@ -2256,6 +2264,15 @@ vips_foreign_operation_init( void )
vips_foreign_load_svg_source_get_type();
#endif /*HAVE_RSVG*/
#ifdef HAVE_LIBJXL
vips_foreign_load_jxl_file_get_type();
vips_foreign_load_jxl_buffer_get_type();
vips_foreign_load_jxl_source_get_type();
vips_foreign_save_jxl_file_get_type();
vips_foreign_save_jxl_buffer_get_type();
vips_foreign_save_jxl_target_get_type();
#endif /*HAVE_LIBJXL*/
#ifdef HAVE_LIBOPENJP2
vips_foreign_load_jp2k_file_get_type();
vips_foreign_load_jp2k_buffer_get_type();

View File

@ -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 ) )

View File

@ -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().
*

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 );
}

File diff suppressed because it is too large Load Diff

View File

@ -224,18 +224,39 @@ static inline lzw_result lzw__read_code(
/**
* Clear LZW code table.
* Handle clear code.
*
* \param[in] ctx LZW reading context, updated.
* \param[in] ctx LZW reading context, updated.
* \param[out] code_out Returns next code after a clear code.
* \return LZW_OK or error code.
*/
static inline void lzw__clear_table(
struct lzw_ctx *ctx)
static inline lzw_result lzw__handle_clear(
struct lzw_ctx *ctx,
uint32_t *code_out)
{
uint32_t code;
/* Reset table building context */
ctx->code_size = ctx->initial_code_size;
ctx->code_max = (1 << ctx->initial_code_size) - 1;
ctx->table_size = ctx->eoi_code + 1;
/* There might be a sequence of clear codes, so process them all */
do {
lzw_result res = lzw__read_code(&ctx->input,
ctx->code_size, &code);
if (res != LZW_OK) {
return res;
}
} while (code == ctx->clear_code);
/* The initial code must be from the initial table. */
if (code > ctx->clear_code) {
return LZW_BAD_ICODE;
}
*code_out = code;
return LZW_OK;
}
@ -248,6 +269,8 @@ lzw_result lzw_decode_init(
uint8_t minimum_code_size)
{
struct lzw_table_entry *table = ctx->table;
lzw_result res;
uint32_t code;
if (minimum_code_size >= LZW_CODE_MAX) {
return LZW_BAD_ICODE;
@ -276,8 +299,19 @@ lzw_result lzw_decode_init(
table[i].count = 1;
}
lzw__clear_table(ctx);
ctx->prev_code = ctx->clear_code;
res = lzw__handle_clear(ctx, &code);
if (res != LZW_OK) {
return res;
}
/* Store details of this code as "previous code" to the context. */
ctx->prev_code_first = ctx->table[code].first;
ctx->prev_code_count = ctx->table[code].count;
ctx->prev_code = code;
/* Add code to context for immediate output. */
ctx->output_code = code;
ctx->output_left = 1;
return LZW_OK;
}
@ -345,27 +379,27 @@ static inline lzw_result lzw__decode(struct lzw_ctx *ctx,
return LZW_BAD_CODE;
} else if (code == ctx->clear_code) {
lzw__clear_table(ctx);
} else {
if (ctx->prev_code != ctx->clear_code &&
ctx->table_size < LZW_TABLE_ENTRY_MAX) {
uint32_t size = ctx->table_size;
lzw__table_add_entry(ctx, (code < size) ?
ctx->table[code].first :
ctx->prev_code_first);
/* Ensure code size is increased, if needed. */
if (size == ctx->code_max &&
ctx->code_size < LZW_CODE_MAX) {
ctx->code_size++;
ctx->code_max = (1 << ctx->code_size) - 1;
}
res = lzw__handle_clear(ctx, &code);
if (res != LZW_OK) {
return res;
}
*used += write_pixels(ctx, output, length, *used, code,
ctx->table[code].count);
} else if (ctx->table_size < LZW_TABLE_ENTRY_MAX) {
uint32_t size = ctx->table_size;
lzw__table_add_entry(ctx, (code < size) ?
ctx->table[code].first :
ctx->prev_code_first);
/* Ensure code size is increased, if needed. */
if (size == ctx->code_max && ctx->code_size < LZW_CODE_MAX) {
ctx->code_size++;
ctx->code_max = (1 << ctx->code_size) - 1;
}
}
*used += write_pixels(ctx, output, length, *used, code,
ctx->table[code].count);
/* Store details of this code as "previous code" to the context. */
ctx->prev_code_first = ctx->table[code].first;
ctx->prev_code_count = ctx->table[code].count;
@ -428,20 +462,9 @@ static inline uint32_t lzw__write_pixels(struct lzw_ctx *ctx,
return count;
}
/* Exported function, documented in lzw.h */
lzw_result lzw_decode(struct lzw_ctx *ctx,
const uint8_t *restrict* const restrict data,
uint32_t *restrict used)
{
*used = 0;
*data = ctx->stack_base;
return lzw__decode(ctx, ctx->stack_base, sizeof(ctx->stack_base),
lzw__write_pixels, used);
}
/* Exported function, documented in lzw.h */
lzw_result lzw_decode_continuous(struct lzw_ctx *ctx,
const uint8_t ** const data,
const uint8_t *restrict *const restrict data,
uint32_t *restrict used)
{
*used = 0;

View File

@ -76,21 +76,6 @@ lzw_result lzw_decode_init(
uint32_t compressed_data_pos,
uint8_t minimum_code_size);
/**
* Read a single LZW code and write into lzw context owned output buffer.
*
* Ensure anything in output is used before calling this, as anything
* on the there before this call will be trampled.
*
* \param[in] ctx LZW reading context, updated.
* \param[out] data Returns pointer to array of output values.
* \param[out] used Returns the number of values written to data.
* \return LZW_OK on success, or appropriate error code otherwise.
*/
lzw_result lzw_decode(struct lzw_ctx *ctx,
const uint8_t *restrict *const restrict data,
uint32_t *restrict used);
/**
* Read input codes until end of lzw context owned output buffer.
*
@ -103,7 +88,7 @@ lzw_result lzw_decode(struct lzw_ctx *ctx,
* \return LZW_OK on success, or appropriate error code otherwise.
*/
lzw_result lzw_decode_continuous(struct lzw_ctx *ctx,
const uint8_t ** const data,
const uint8_t *restrict *const restrict data,
uint32_t *restrict used);
/**

View File

@ -1,23 +1,13 @@
--- libnsgif-orig.c 2021-02-28 14:10:41.818557190 +0000
+++ libnsgif.c 2021-02-28 14:11:55.942285930 +0000
@@ -435,20 +435,15 @@
block_size = gif_data[0] + 1;
/* Check if the frame data runs off the end of the file */
if ((int)(gif_bytes - block_size) < 0) {
- /* Try to recover by signaling the end of the gif.
- * Once we get garbage data, there is no logical way to
- * determine where the next frame is. It's probably
- * better to partially load the gif than not at all.
- */
- if (gif_bytes >= 2) {
- gif_data[0] = 0;
- gif_data[1] = GIF_TRAILER;
- gif_bytes = 1;
- ++gif_data;
- break;
- } else {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
--- libnsgif.c.orig 2021-04-24 18:33:02.757323861 +0100
+++ libnsgif.c 2021-04-24 18:35:14.659860190 +0100
@@ -424,20 +424,15 @@
block_size = gif_data[0] + 1;
/* Check if the frame data runs off the end of the file */
if ((int)(gif_bytes - block_size) < 0) {
- /* Try to recover by signaling the end of the gif.
- * Once we get garbage data, there is no logical way to
- * determine where the next frame is. It's probably
- * better to partially load the gif than not at all.
+ /* jcupitt 15/9/19
+ *
+ * There was code here to set a TRAILER tag. But this
@ -25,8 +15,17 @@
+ * libvips, where buffers can be mmaped read only files.
+ *
+ * Instead, just signal insufficient frame data.
+ */
*/
- if (gif_bytes >= 2) {
- gif_data[0] = 0;
- gif_data[1] = GIF_TRAILER;
- gif_bytes = 1;
- ++gif_data;
- break;
- } else {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
+ return GIF_INSUFFICIENT_FRAME_DATA;
} else {
gif_bytes -= block_size;
gif_data += block_size;
} else {
gif_bytes -= block_size;
gif_data += block_size;

View File

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

View File

@ -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;

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, ... )
__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