Merge branch 'master' into gmodulized
This commit is contained in:
commit
023f74b037
@ -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
|
||||
|
||||
|
15
README.md
15
README.md
@ -22,10 +22,11 @@ operations, frequency filtering, colour, resampling,
|
||||
statistics and others. It supports a large range of [numeric
|
||||
types](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsBandFormat),
|
||||
from 8-bit int to 128-bit complex. Images can have any number of bands.
|
||||
It supports a good range of image formats, including JPEG, JPEG2000, TIFF, PNG,
|
||||
WebP, HEIC, AVIF, FITS, Matlab, OpenEXR, PDF, SVG, HDR, PPM / PGM / PFM,
|
||||
CSV, GIF, Analyze, NIfTI, DeepZoom, and OpenSlide. It can also load images
|
||||
via ImageMagick or GraphicsMagick, letting it work with formats like DICOM.
|
||||
It supports a good range of image formats, including JPEG, JPEG2000, JPEG-XL,
|
||||
TIFF, PNG, WebP, HEIC, AVIF, FITS, Matlab, OpenEXR, PDF, SVG, HDR, PPM / PGM /
|
||||
PFM, CSV, GIF, Analyze, NIfTI, DeepZoom, and OpenSlide. It can also load
|
||||
images via ImageMagick or GraphicsMagick, letting it work with formats
|
||||
like DICOM.
|
||||
|
||||
It comes with bindings for
|
||||
[C](https://libvips.github.io/libvips/API/current/using-from-c.html),
|
||||
@ -47,7 +48,7 @@ Rails](https://edgeguides.rubyonrails.org/active_storage_overview.html),
|
||||
[mediawiki](https://www.mediawiki.org/wiki/Extension:VipsScaler),
|
||||
[PhotoFlow](https://github.com/aferrero2707/PhotoFlow) and others.
|
||||
The official libvips GUI is [nip2](https://github.com/libvips/nip2),
|
||||
a strange combination of a spreadsheet and an photo editor.
|
||||
a strange combination of a spreadsheet and a photo editor.
|
||||
|
||||
# Install
|
||||
|
||||
@ -278,6 +279,10 @@ OpenEXR images.
|
||||
|
||||
If available, libvips will read and write JPEG2000 images.
|
||||
|
||||
### libjxl
|
||||
|
||||
If available, libvips will read and write JPEG-XL images.
|
||||
|
||||
### OpenSlide
|
||||
|
||||
If available, libvips can load OpenSlide-supported virtual slide
|
||||
|
25
configure.ac
25
configure.ac
@ -809,6 +809,28 @@ VIPS_CFLAGS="$VIPS_CFLAGS $NIFTI_CFLAGS"
|
||||
VIPS_INCLUDES="$VIPS_INCLUDES $NIFTI_INCLUDES"
|
||||
VIPS_LIBS="$VIPS_LIBS $NIFTI_LIBS"
|
||||
|
||||
# jpeg-xl
|
||||
AC_ARG_WITH([libjxl],
|
||||
AS_HELP_STRING([--without-libjxl],
|
||||
[build without libjxl (default: test)]))
|
||||
|
||||
if test x"$with_libjxl" != x"no"; then
|
||||
PKG_CHECK_MODULES(LIBJXL, libjxl_threads >= 0.3.7 libjxl >= 0.3.7,
|
||||
[AC_DEFINE(HAVE_LIBJXL,1,
|
||||
[define if you have libjxl >= 0.3.7 installed.])
|
||||
with_libjxl=yes
|
||||
PACKAGES_USED="$PACKAGES_USED libjxl"
|
||||
],
|
||||
[AC_MSG_WARN([libjxl not found; disabling libjxl support])
|
||||
with_libjxl=no
|
||||
]
|
||||
)
|
||||
fi
|
||||
|
||||
VIPS_CFLAGS="$VIPS_CFLAGS $LIBJXL_CFLAGS"
|
||||
VIPS_INCLUDES="$VIPS_INCLUDES $LIBJXL_INCLUDES"
|
||||
VIPS_LIBS="$VIPS_LIBS $LIBJXL_LIBS"
|
||||
|
||||
# openjpeg
|
||||
AC_ARG_WITH([libopenjp2],
|
||||
AS_HELP_STRING([--without-libopenjp2],
|
||||
@ -1531,6 +1553,9 @@ EXIF metadata support with libexif: $with_libexif
|
||||
|
||||
## File format support
|
||||
JPEG load/save with libjpeg: $with_jpeg
|
||||
JXL load/save with libjxl: $with_libjxl
|
||||
JPEG2000 load/save with libopenjp2: $with_libopenjp2
|
||||
(requires libopenjp2 2.2 or later)
|
||||
PNG load with libspng: $with_libspng
|
||||
(requires libspng-0.6 or later)
|
||||
PNG load/save with libpng: $with_png
|
||||
|
@ -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.
|
||||
|
@ -19,6 +19,8 @@ libforeign_la_SOURCES = \
|
||||
foreign.c \
|
||||
heifload.c \
|
||||
heifsave.c \
|
||||
jxlload.c \
|
||||
jxlsave.c \
|
||||
jp2kload.c \
|
||||
jp2ksave.c \
|
||||
jpeg2vips.c \
|
||||
|
@ -366,7 +366,8 @@ vips_gsf_path( VipsGsfDirectory *tree, const char *name, ... )
|
||||
* path we are creating.
|
||||
*/
|
||||
tree->file_count += 1;
|
||||
tree->filename_lengths += strlen( tree->out->name ) + strlen( name ) + 1;
|
||||
tree->filename_lengths +=
|
||||
strlen( tree->out->name ) + strlen( name ) + 1;
|
||||
|
||||
dir = tree;
|
||||
va_start( ap, name );
|
||||
|
@ -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();
|
||||
|
@ -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 ) )
|
||||
|
@ -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
973
libvips/foreign/jxlload.c
Normal 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
823
libvips/foreign/jxlsave.c
Normal 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
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
@ -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*/
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user