reorganise loadable modules
- heifload operation now defined in heifload.c, etc. - C API wrappers moved to foreign.c
This commit is contained in:
parent
f637206e8d
commit
def025da3e
@ -132,22 +132,22 @@ vips_magick_la_CPPFLAGS = $(MODULE_CPPFLAGS) $(MAGICK_CFLAGS)
|
||||
vips_magick_la_LDFLAGS = $(MODULE_LDFLAGS)
|
||||
vips_magick_la_LIBADD = $(MODULE_LIBADD) $(MAGICK_LIBS)
|
||||
|
||||
vips_jxl_la_SOURCES = module/jxl.c foreign/jxl2vips.c foreign/vips2jxl.c
|
||||
vips_jxl_la_SOURCES = module/jxl.c foreign/jxlload.c foreign/jxlsave.c
|
||||
vips_jxl_la_CPPFLAGS = $(MODULE_CPPFLAGS) $(LIBJXL_CFLAGS)
|
||||
vips_jxl_la_LDFLAGS = $(MODULE_LDFLAGS)
|
||||
vips_jxl_la_LIBADD = $(MODULE_LIBADD) $(LIBJXL_LIBS)
|
||||
|
||||
vips_heif_la_SOURCES = module/heif.c foreign/heif2vips.c foreign/vips2heif.c
|
||||
vips_heif_la_SOURCES = module/heif.c foreign/heifload.c foreign/heifsave.c
|
||||
vips_heif_la_CPPFLAGS = $(MODULE_CPPFLAGS) $(HEIF_CFLAGS)
|
||||
vips_heif_la_LDFLAGS = $(MODULE_LDFLAGS)
|
||||
vips_heif_la_LIBADD = $(MODULE_LIBADD) $(HEIF_LIBS)
|
||||
|
||||
vips_poppler_la_SOURCES = module/poppler.c foreign/poppler2vips.c
|
||||
vips_poppler_la_SOURCES = module/poppler.c foreign/popplerload.c
|
||||
vips_poppler_la_CPPFLAGS = $(MODULE_CPPFLAGS) $(POPPLER_CFLAGS)
|
||||
vips_poppler_la_LDFLAGS = $(MODULE_LDFLAGS)
|
||||
vips_poppler_la_LIBADD = $(MODULE_LIBADD) $(POPPLER_LIBS)
|
||||
|
||||
vips_openslide_la_SOURCES = module/openslide.c foreign/openslide2vips.c
|
||||
vips_openslide_la_SOURCES = module/openslide.c foreign/openslideload.c
|
||||
vips_openslide_la_CPPFLAGS = $(MODULE_CPPFLAGS) $(OPENSLIDE_CFLAGS)
|
||||
vips_openslide_la_LDFLAGS = $(MODULE_LDFLAGS)
|
||||
vips_openslide_la_LIBADD = $(MODULE_LIBADD) $(OPENSLIDE_LIBS)
|
||||
|
@ -15,10 +15,6 @@ libforeign_la_SOURCES = \
|
||||
fitsload.c \
|
||||
fitssave.c \
|
||||
foreign.c \
|
||||
heifload.c \
|
||||
heifsave.c \
|
||||
jxlload.c \
|
||||
jxlsave.c \
|
||||
jp2kload.c \
|
||||
jp2ksave.c \
|
||||
jpeg2vips.c \
|
||||
@ -36,9 +32,7 @@ libforeign_la_SOURCES = \
|
||||
nsgifload.c \
|
||||
openexr2vips.c \
|
||||
openexrload.c \
|
||||
openslideload.c \
|
||||
pdfiumload.c \
|
||||
pdfload.c \
|
||||
pforeign.h \
|
||||
pngload.c \
|
||||
pngsave.c \
|
||||
@ -81,19 +75,19 @@ libforeign_la_SOURCES += \
|
||||
endif # !MAGICK_MODULE
|
||||
|
||||
if !LIBJXL_MODULE
|
||||
libforeign_la_SOURCES += jxl2vips.c vips2jxl.c
|
||||
libforeign_la_SOURCES += jxlload.c jxlsave.c
|
||||
endif # !LIBJXL_MODULE
|
||||
|
||||
if !HEIF_MODULE
|
||||
libforeign_la_SOURCES += heif2vips.c vips2heif.c
|
||||
libforeign_la_SOURCES += heifload.c heifsave.c
|
||||
endif # !HEIF_MODULE
|
||||
|
||||
if !POPPLER_MODULE
|
||||
libforeign_la_SOURCES += poppler2vips.c
|
||||
libforeign_la_SOURCES += popplerload.c
|
||||
endif # !POPPLER_MODULE
|
||||
|
||||
if !OPENSLIDE_MODULE
|
||||
libforeign_la_SOURCES += openslide2vips.c
|
||||
libforeign_la_SOURCES += openslideload.c
|
||||
endif # !OPENSLIDE_MODULE
|
||||
|
||||
AM_CPPFLAGS = -I${top_srcdir}/libvips/include @VIPS_CFLAGS@ @VIPS_INCLUDES@
|
||||
|
@ -2080,6 +2080,688 @@ vips_foreign_find_save_buffer( const char *name )
|
||||
return( G_OBJECT_CLASS_NAME( save_class ) );
|
||||
}
|
||||
|
||||
/* C API wrappers for loadable modules go here.
|
||||
*/
|
||||
|
||||
/**
|
||||
* vips_heifload:
|
||||
* @filename: file to load
|
||||
* @out: (out): decompressed image
|
||||
* @...: %NULL-terminated list of optional named arguments
|
||||
*
|
||||
* Optional arguments:
|
||||
*
|
||||
* * @page: %gint, page (top-level image number) to read
|
||||
* * @n: %gint, load this many pages
|
||||
* * @thumbnail: %gboolean, fetch thumbnail instead of image
|
||||
*
|
||||
* Read a HEIF image file into a VIPS image.
|
||||
*
|
||||
* Use @page to select a page to render, numbering from zero. If neither @n
|
||||
* nor @page are set, @page defaults to the primary page, otherwise to 0.
|
||||
*
|
||||
* Use @n to select the number of pages to render. The default is 1. Pages are
|
||||
* rendered in a vertical column. Set to -1 to mean "until the end of the
|
||||
* document". Use vips_grid() to reorganise pages.
|
||||
*
|
||||
* HEIF images have a primary image. The metadata item `heif-primary` gives
|
||||
* the page number of the primary.
|
||||
*
|
||||
* If @thumbnail is %TRUE, then fetch a stored thumbnail rather than the
|
||||
* image.
|
||||
*
|
||||
* See also: vips_image_new_from_file().
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
*/
|
||||
int
|
||||
vips_heifload( const char *filename, VipsImage **out, ... )
|
||||
{
|
||||
va_list ap;
|
||||
int result;
|
||||
|
||||
va_start( ap, out );
|
||||
result = vips_call_split( "heifload", ap, filename, out );
|
||||
va_end( ap );
|
||||
|
||||
return( result );
|
||||
}
|
||||
|
||||
/**
|
||||
* vips_heifload_buffer:
|
||||
* @buf: (array length=len) (element-type guint8): memory area to load
|
||||
* @len: (type gsize): size of memory area
|
||||
* @out: (out): image to write
|
||||
* @...: %NULL-terminated list of optional named arguments
|
||||
*
|
||||
* Optional arguments:
|
||||
*
|
||||
* * @page: %gint, page (top-level image number) to read
|
||||
* * @n: %gint, load this many pages
|
||||
* * @thumbnail: %gboolean, fetch thumbnail instead of image
|
||||
*
|
||||
* Read a HEIF image file into a VIPS image.
|
||||
* Exactly as vips_heifload(), but read from a memory buffer.
|
||||
*
|
||||
* You must not free the buffer while @out is active. The
|
||||
* #VipsObject::postclose signal on @out is a good place to free.
|
||||
*
|
||||
* See also: vips_heifload().
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
*/
|
||||
int
|
||||
vips_heifload_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( "heifload_buffer", ap, blob, out );
|
||||
va_end( ap );
|
||||
|
||||
vips_area_unref( VIPS_AREA( blob ) );
|
||||
|
||||
return( result );
|
||||
}
|
||||
|
||||
/**
|
||||
* vips_heifload_source:
|
||||
* @source: source to load from
|
||||
* @out: (out): image to write
|
||||
* @...: %NULL-terminated list of optional named arguments
|
||||
*
|
||||
* Optional arguments:
|
||||
*
|
||||
* * @page: %gint, page (top-level image number) to read
|
||||
* * @n: %gint, load this many pages
|
||||
* * @thumbnail: %gboolean, fetch thumbnail instead of image
|
||||
*
|
||||
* Exactly as vips_heifload(), but read from a source.
|
||||
*
|
||||
* See also: vips_heifload().
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
*/
|
||||
int
|
||||
vips_heifload_source( VipsSource *source, VipsImage **out, ... )
|
||||
{
|
||||
va_list ap;
|
||||
int result;
|
||||
|
||||
va_start( ap, out );
|
||||
result = vips_call_split( "heifload_source", ap, source, out );
|
||||
va_end( ap );
|
||||
|
||||
return( result );
|
||||
}
|
||||
|
||||
/**
|
||||
* vips_heifsave: (method)
|
||||
* @in: image to save
|
||||
* @filename: file to write to
|
||||
* @...: %NULL-terminated list of optional named arguments
|
||||
*
|
||||
* Optional arguments:
|
||||
*
|
||||
* * @Q: %gint, quality factor
|
||||
* * @lossless: %gboolean, enable lossless encoding
|
||||
* * @compression: #VipsForeignHeifCompression, write with this compression
|
||||
* * @speed: %gint, encoding speed
|
||||
* * @subsample_mode: #VipsForeignSubsample, chroma subsampling mode
|
||||
*
|
||||
* Write a VIPS image to a file in HEIF format.
|
||||
*
|
||||
* Use @Q to set the compression factor. Default 50, which seems to be roughly
|
||||
* what the iphone uses. Q 30 gives about the same quality as JPEG Q 75.
|
||||
*
|
||||
* Set @lossless %TRUE to switch to lossless compression.
|
||||
*
|
||||
* Use @compression to set the encoder e.g. HEVC, AVC, AV1. It defaults to AV1
|
||||
* if the target filename ends with ".avif", otherwise HEVC.
|
||||
*
|
||||
* Use @speed to control the CPU effort spent improving compression.
|
||||
* This is currently only applicable to AV1 encoders. Defaults to 5, 0 is
|
||||
* slowest, 9 is fastest.
|
||||
*
|
||||
* Chroma subsampling is normally automatically disabled for Q >= 90. You can
|
||||
* force the subsampling mode with @subsample_mode.
|
||||
*
|
||||
* See also: vips_image_write_to_file(), vips_heifload().
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
*/
|
||||
int
|
||||
vips_heifsave( VipsImage *in, const char *filename, ... )
|
||||
{
|
||||
va_list ap;
|
||||
int result;
|
||||
|
||||
va_start( ap, filename );
|
||||
result = vips_call_split( "heifsave", ap, in, filename );
|
||||
va_end( ap );
|
||||
|
||||
return( result );
|
||||
}
|
||||
|
||||
/**
|
||||
* vips_heifsave_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:
|
||||
*
|
||||
* * @Q: %gint, quality factor
|
||||
* * @lossless: %gboolean, enable lossless encoding
|
||||
* * @compression: #VipsForeignHeifCompression, write with this compression
|
||||
* * @speed: %gint, encoding speed
|
||||
* * @subsample_mode: #VipsForeignSubsample, chroma subsampling mode
|
||||
*
|
||||
* As vips_heifsave(), but save to a memory buffer.
|
||||
*
|
||||
* The address of the buffer is returned in @obuf, the length of the buffer in
|
||||
* @olen. You are responsible for freeing the buffer with g_free() when you
|
||||
* are done with it.
|
||||
*
|
||||
* See also: vips_heifsave(), vips_image_write_to_file().
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
*/
|
||||
int
|
||||
vips_heifsave_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( "heifsave_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_heifsave_target: (method)
|
||||
* @in: image to save
|
||||
* @target: save image to this target
|
||||
* @...: %NULL-terminated list of optional named arguments
|
||||
*
|
||||
* Optional arguments:
|
||||
*
|
||||
* * @Q: %gint, quality factor
|
||||
* * @lossless: %gboolean, enable lossless encoding
|
||||
* * @compression: #VipsForeignHeifCompression, write with this compression
|
||||
* * @speed: %gint, encoding speed
|
||||
* * @subsample_mode: #VipsForeignSubsample, chroma subsampling mode
|
||||
*
|
||||
* As vips_heifsave(), but save to a target.
|
||||
*
|
||||
* See also: vips_heifsave(), vips_image_write_to_target().
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
*/
|
||||
int
|
||||
vips_heifsave_target( VipsImage *in, VipsTarget *target, ... )
|
||||
{
|
||||
va_list ap;
|
||||
int result;
|
||||
|
||||
va_start( ap, target );
|
||||
result = vips_call_split( "heifsave_target", ap, in, target );
|
||||
va_end( ap );
|
||||
|
||||
return( result );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* vips_pdfload:
|
||||
* @filename: file to load
|
||||
* @out: (out): output image
|
||||
* @...: %NULL-terminated list of optional named arguments
|
||||
*
|
||||
* Optional arguments:
|
||||
*
|
||||
* * @page: %gint, load this page, numbered from zero
|
||||
* * @n: %gint, load this many pages
|
||||
* * @dpi: %gdouble, render at this DPI
|
||||
* * @scale: %gdouble, scale render by this factor
|
||||
* * @background: #VipsArrayDouble background colour
|
||||
*
|
||||
* Render a PDF file into a VIPS image.
|
||||
*
|
||||
* The output image is always RGBA --- CMYK PDFs will be
|
||||
* converted. If you need CMYK bitmaps, you should use vips_magickload()
|
||||
* instead.
|
||||
*
|
||||
* Use @page to select a page to render, numbering from zero.
|
||||
*
|
||||
* Use @n to select the number of pages to render. The default is 1. Pages are
|
||||
* rendered in a vertical column, with each individual page aligned to the
|
||||
* left. Set to -1 to mean "until the end of the document". Use vips_grid()
|
||||
* to change page layout.
|
||||
*
|
||||
* Use @dpi to set the rendering resolution. The default is 72. Additionally,
|
||||
* you can scale by setting @scale. If you set both, they combine.
|
||||
*
|
||||
* Use @background to set the background RGBA colour. The default is 255
|
||||
* (solid white), use eg. 0 for a transparent background.
|
||||
*
|
||||
* The operation fills a number of header fields with metadata, for example
|
||||
* "pdf-author". They may be useful.
|
||||
*
|
||||
* This function only reads the image header and does not render any pixel
|
||||
* data. Rendering occurs when pixels are accessed.
|
||||
*
|
||||
* See also: vips_image_new_from_file(), vips_magickload().
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
*/
|
||||
int
|
||||
vips_pdfload( const char *filename, VipsImage **out, ... )
|
||||
{
|
||||
va_list ap;
|
||||
int result;
|
||||
|
||||
va_start( ap, out );
|
||||
result = vips_call_split( "pdfload", ap, filename, out );
|
||||
va_end( ap );
|
||||
|
||||
return( result );
|
||||
}
|
||||
|
||||
/**
|
||||
* vips_pdfload_buffer:
|
||||
* @buf: (array length=len) (element-type guint8): memory area to load
|
||||
* @len: (type gsize): size of memory area
|
||||
* @out: (out): image to write
|
||||
* @...: %NULL-terminated list of optional named arguments
|
||||
*
|
||||
* Optional arguments:
|
||||
*
|
||||
* * @page: %gint, load this page, numbered from zero
|
||||
* * @n: %gint, load this many pages
|
||||
* * @dpi: %gdouble, render at this DPI
|
||||
* * @scale: %gdouble, scale render by this factor
|
||||
* * @background: #VipsArrayDouble background colour
|
||||
*
|
||||
* Read a PDF-formatted memory buffer into a VIPS image. Exactly as
|
||||
* vips_pdfload(), but read from memory.
|
||||
*
|
||||
* You must not free the buffer while @out is active. The
|
||||
* #VipsObject::postclose signal on @out is a good place to free.
|
||||
*
|
||||
* See also: vips_pdfload().
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
*/
|
||||
int
|
||||
vips_pdfload_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( "pdfload_buffer", ap, blob, out );
|
||||
va_end( ap );
|
||||
|
||||
vips_area_unref( VIPS_AREA( blob ) );
|
||||
|
||||
return( result );
|
||||
}
|
||||
|
||||
/**
|
||||
* vips_pdfload_source:
|
||||
* @source: source to load from
|
||||
* @out: (out): image to write
|
||||
* @...: %NULL-terminated list of optional named arguments
|
||||
*
|
||||
* Optional arguments:
|
||||
*
|
||||
* * @page: %gint, load this page, numbered from zero
|
||||
* * @n: %gint, load this many pages
|
||||
* * @dpi: %gdouble, render at this DPI
|
||||
* * @scale: %gdouble, scale render by this factor
|
||||
* * @background: #VipsArrayDouble background colour
|
||||
*
|
||||
* Exactly as vips_pdfload(), but read from a source.
|
||||
*
|
||||
* See also: vips_pdfload()
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
*/
|
||||
int
|
||||
vips_pdfload_source( VipsSource *source, VipsImage **out, ... )
|
||||
{
|
||||
va_list ap;
|
||||
int result;
|
||||
|
||||
va_start( ap, out );
|
||||
result = vips_call_split( "pdfload_source", ap, source, out );
|
||||
va_end( ap );
|
||||
|
||||
return( result );
|
||||
}
|
||||
|
||||
/**
|
||||
* vips_openslideload:
|
||||
* @filename: file to load
|
||||
* @out: (out): decompressed image
|
||||
* @...: %NULL-terminated list of optional named arguments
|
||||
*
|
||||
* Optional arguments:
|
||||
*
|
||||
* * @level: %gint, load this level
|
||||
* * @associated: %gchararray, load this associated image
|
||||
* * @attach_associated: %gboolean, attach all associated images as metadata
|
||||
* * @autocrop: %gboolean, crop to image bounds
|
||||
*
|
||||
* Read a virtual slide supported by the OpenSlide library into a VIPS image.
|
||||
* OpenSlide supports images in Aperio, Hamamatsu, MIRAX, Sakura, Trestle,
|
||||
* and Ventana formats.
|
||||
*
|
||||
* To facilitate zooming, virtual slide formats include multiple scaled-down
|
||||
* versions of the high-resolution image. These are typically called
|
||||
* "levels". By default, vips_openslideload() reads the highest-resolution
|
||||
* level (level 0). Set @level to the level number you want.
|
||||
*
|
||||
* In addition to the slide image itself, virtual slide formats sometimes
|
||||
* include additional images, such as a scan of the slide's barcode.
|
||||
* OpenSlide calls these "associated images". To read an associated image,
|
||||
* set @associated to the image's name.
|
||||
* A slide's associated images are listed in the
|
||||
* "slide-associated-images" metadata item.
|
||||
*
|
||||
* If you set @attach_associated, then all associated images are attached as
|
||||
* metadata items. Use vips_image_get_image() on @out to retrieve them. Images
|
||||
* are attached as "openslide-associated-XXXXX", where XXXXX is the name of the
|
||||
* associated image.
|
||||
*
|
||||
* The output of this operator is always RGBA.
|
||||
*
|
||||
* See also: vips_image_new_from_file().
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
*/
|
||||
int
|
||||
vips_openslideload( const char *filename, VipsImage **out, ... )
|
||||
{
|
||||
va_list ap;
|
||||
int result;
|
||||
|
||||
va_start( ap, out );
|
||||
result = vips_call_split( "openslideload", ap, filename, out );
|
||||
va_end( ap );
|
||||
|
||||
return( result );
|
||||
}
|
||||
|
||||
/**
|
||||
* vips_openslideload_source:
|
||||
* @source: source to load from
|
||||
* @out: (out): decompressed image
|
||||
* @...: %NULL-terminated list of optional named arguments
|
||||
*
|
||||
* Optional arguments:
|
||||
*
|
||||
* * @level: %gint, load this level
|
||||
* * @associated: %gchararray, load this associated image
|
||||
* * @attach_associated: %gboolean, attach all associated images as metadata
|
||||
* * @autocrop: %gboolean, crop to image bounds
|
||||
*
|
||||
* Exactly as vips_openslideload(), but read from a source.
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
*/
|
||||
int
|
||||
vips_openslideload_source( VipsSource *source, VipsImage **out, ... )
|
||||
{
|
||||
va_list ap;
|
||||
int result;
|
||||
|
||||
va_start( ap, out );
|
||||
result = vips_call_split( "openslideload_source", ap, source, out );
|
||||
va_end( ap );
|
||||
|
||||
return( result );
|
||||
}
|
||||
|
||||
/* Called from iofuncs to init all operations in this dir. Use a plugin system
|
||||
* instead?
|
||||
*/
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -49,6 +49,8 @@
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
#include <vips/intl.h>
|
||||
|
||||
#ifdef HAVE_HEIF_ENCODER
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@ -56,136 +58,665 @@
|
||||
#include <vips/vips.h>
|
||||
#include <vips/internal.h>
|
||||
|
||||
/**
|
||||
* vips_heifsave: (method)
|
||||
* @in: image to save
|
||||
* @filename: file to write to
|
||||
* @...: %NULL-terminated list of optional named arguments
|
||||
*
|
||||
* Optional arguments:
|
||||
*
|
||||
* * @Q: %gint, quality factor
|
||||
* * @lossless: %gboolean, enable lossless encoding
|
||||
* * @compression: #VipsForeignHeifCompression, write with this compression
|
||||
* * @speed: %gint, encoding speed
|
||||
* * @subsample_mode: #VipsForeignSubsample, chroma subsampling mode
|
||||
*
|
||||
* Write a VIPS image to a file in HEIF format.
|
||||
*
|
||||
* Use @Q to set the compression factor. Default 50, which seems to be roughly
|
||||
* what the iphone uses. Q 30 gives about the same quality as JPEG Q 75.
|
||||
*
|
||||
* Set @lossless %TRUE to switch to lossless compression.
|
||||
*
|
||||
* Use @compression to set the encoder e.g. HEVC, AVC, AV1. It defaults to AV1
|
||||
* if the target filename ends with ".avif", otherwise HEVC.
|
||||
*
|
||||
* Use @speed to control the CPU effort spent improving compression.
|
||||
* This is currently only applicable to AV1 encoders. Defaults to 5, 0 is
|
||||
* slowest, 9 is fastest.
|
||||
*
|
||||
* Chroma subsampling is normally automatically disabled for Q >= 90. You can
|
||||
* force the subsampling mode with @subsample_mode.
|
||||
*
|
||||
* See also: vips_image_write_to_file(), vips_heifload().
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
#include "pforeign.h"
|
||||
|
||||
#include <libheif/heif.h>
|
||||
|
||||
typedef struct _VipsForeignSaveHeif {
|
||||
VipsForeignSave parent_object;
|
||||
|
||||
/* Where to write (set by subclasses).
|
||||
*/
|
||||
VipsTarget *target;
|
||||
|
||||
/* Coding quality factor (1-100).
|
||||
*/
|
||||
int Q;
|
||||
|
||||
/* Lossless compression.
|
||||
*/
|
||||
gboolean lossless;
|
||||
|
||||
/* Compression format
|
||||
*/
|
||||
VipsForeignHeifCompression compression;
|
||||
|
||||
/* CPU effort (0-8).
|
||||
*/
|
||||
int speed;
|
||||
|
||||
/* Chroma subsampling.
|
||||
*/
|
||||
VipsForeignSubsample subsample_mode;
|
||||
|
||||
/* The image we save. This is a copy of save->ready since we need to
|
||||
* be able to update the metadata.
|
||||
*/
|
||||
VipsImage *image;
|
||||
|
||||
int page_width;
|
||||
int page_height;
|
||||
int n_pages;
|
||||
|
||||
struct heif_context *ctx;
|
||||
struct heif_encoder *encoder;
|
||||
|
||||
/* The current page we are writing.
|
||||
*/
|
||||
struct heif_image_handle *handle;
|
||||
|
||||
/* The current page in memory which we build as we scan down the
|
||||
* image.
|
||||
*/
|
||||
struct heif_image *img;
|
||||
|
||||
/* The libheif memory area we fill with pixels from the libvips
|
||||
* pipe.
|
||||
*/
|
||||
uint8_t *data;
|
||||
int stride;
|
||||
|
||||
} VipsForeignSaveHeif;
|
||||
|
||||
typedef VipsForeignSaveClass VipsForeignSaveHeifClass;
|
||||
|
||||
/* Defined in heif2vips.c
|
||||
*/
|
||||
int
|
||||
vips_heifsave( VipsImage *in, const char *filename, ... )
|
||||
void vips__heif_error( struct heif_error *error );
|
||||
void vips__heif_image_print( struct heif_image *img );
|
||||
|
||||
G_DEFINE_ABSTRACT_TYPE( VipsForeignSaveHeif, vips_foreign_save_heif,
|
||||
VIPS_TYPE_FOREIGN_SAVE );
|
||||
|
||||
static void
|
||||
vips_foreign_save_heif_dispose( GObject *gobject )
|
||||
{
|
||||
va_list ap;
|
||||
int result;
|
||||
VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) gobject;
|
||||
|
||||
va_start( ap, filename );
|
||||
result = vips_call_split( "heifsave", ap, in, filename );
|
||||
va_end( ap );
|
||||
VIPS_UNREF( heif->target );
|
||||
VIPS_UNREF( heif->image );
|
||||
VIPS_FREEF( heif_image_release, heif->img );
|
||||
VIPS_FREEF( heif_image_handle_release, heif->handle );
|
||||
VIPS_FREEF( heif_encoder_release, heif->encoder );
|
||||
VIPS_FREEF( heif_context_free, heif->ctx );
|
||||
|
||||
return( result );
|
||||
G_OBJECT_CLASS( vips_foreign_save_heif_parent_class )->
|
||||
dispose( gobject );
|
||||
}
|
||||
|
||||
/**
|
||||
* vips_heifsave_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:
|
||||
*
|
||||
* * @Q: %gint, quality factor
|
||||
* * @lossless: %gboolean, enable lossless encoding
|
||||
* * @compression: #VipsForeignHeifCompression, write with this compression
|
||||
* * @speed: %gint, encoding speed
|
||||
* * @subsample_mode: #VipsForeignSubsample, chroma subsampling mode
|
||||
*
|
||||
* As vips_heifsave(), but save to a memory buffer.
|
||||
*
|
||||
* The address of the buffer is returned in @obuf, the length of the buffer in
|
||||
* @olen. You are responsible for freeing the buffer with g_free() when you
|
||||
* are done with it.
|
||||
*
|
||||
* See also: vips_heifsave(), vips_image_write_to_file().
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
*/
|
||||
int
|
||||
vips_heifsave_buffer( VipsImage *in, void **buf, size_t *len, ... )
|
||||
typedef struct heif_error (*libheif_metadata_fn)( struct heif_context *,
|
||||
const struct heif_image_handle *,
|
||||
const void *, int );
|
||||
|
||||
struct _VipsForeignSaveHeifMetadata {
|
||||
const char *name;
|
||||
libheif_metadata_fn saver;
|
||||
} libheif_metadata[] = {
|
||||
{ VIPS_META_EXIF_NAME, heif_context_add_exif_metadata },
|
||||
{ VIPS_META_XMP_NAME, heif_context_add_XMP_metadata }
|
||||
};
|
||||
|
||||
static int
|
||||
vips_foreign_save_heif_write_metadata( VipsForeignSaveHeif *heif )
|
||||
{
|
||||
va_list ap;
|
||||
VipsArea *area;
|
||||
int result;
|
||||
int i;
|
||||
struct heif_error error;
|
||||
|
||||
area = NULL;
|
||||
/* Rebuild exif from tags, if we'll be saving it.
|
||||
*/
|
||||
if( vips_image_get_typeof( heif->image, VIPS_META_EXIF_NAME ) )
|
||||
if( vips__exif_update( heif->image ) )
|
||||
return( -1 );
|
||||
|
||||
va_start( ap, len );
|
||||
result = vips_call_split( "heifsave_buffer", ap, in, &area );
|
||||
va_end( ap );
|
||||
for( i = 0; i < VIPS_NUMBER( libheif_metadata ); i++ )
|
||||
if( vips_image_get_typeof( heif->image,
|
||||
libheif_metadata[i].name ) ) {
|
||||
const void *data;
|
||||
size_t length;
|
||||
|
||||
if( !result &&
|
||||
area ) {
|
||||
if( buf ) {
|
||||
*buf = area->data;
|
||||
area->free_fn = NULL;
|
||||
#ifdef DEBUG
|
||||
printf( "attaching %s ..\n",
|
||||
libheif_metadata[i].name );
|
||||
#endif /*DEBUG*/
|
||||
|
||||
if( vips_image_get_blob( heif->image,
|
||||
libheif_metadata[i].name, &data, &length ) )
|
||||
return( -1 );
|
||||
|
||||
error = libheif_metadata[i].saver( heif->ctx,
|
||||
heif->handle, data, length );
|
||||
if( error.code ) {
|
||||
vips__heif_error( &error );
|
||||
return( -1 );
|
||||
}
|
||||
}
|
||||
if( len )
|
||||
*len = area->length;
|
||||
|
||||
vips_area_unref( area );
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
static int
|
||||
vips_foreign_save_heif_write_page( VipsForeignSaveHeif *heif, int page )
|
||||
{
|
||||
VipsForeignSave *save = (VipsForeignSave *) heif;
|
||||
|
||||
struct heif_error error;
|
||||
struct heif_encoding_options *options;
|
||||
|
||||
#ifdef HAVE_HEIF_COLOR_PROFILE
|
||||
if( !save->strip &&
|
||||
vips_image_get_typeof( heif->image, VIPS_META_ICC_NAME ) ) {
|
||||
const void *data;
|
||||
size_t length;
|
||||
|
||||
#ifdef DEBUG
|
||||
printf( "attaching profile ..\n" );
|
||||
#endif /*DEBUG*/
|
||||
|
||||
if( vips_image_get_blob( heif->image,
|
||||
VIPS_META_ICC_NAME, &data, &length ) )
|
||||
return( -1 );
|
||||
|
||||
/* FIXME .. also see heif_image_set_nclx_color_profile()
|
||||
*/
|
||||
error = heif_image_set_raw_color_profile( heif->img,
|
||||
"rICC", data, length );
|
||||
if( error.code ) {
|
||||
vips__heif_error( &error );
|
||||
return( -1 );
|
||||
}
|
||||
}
|
||||
#endif /*HAVE_HEIF_COLOR_PROFILE*/
|
||||
|
||||
options = heif_encoding_options_alloc();
|
||||
if( vips_image_hasalpha( heif->image ) )
|
||||
options->save_alpha_channel = 1;
|
||||
|
||||
#ifdef DEBUG
|
||||
printf( "encoding ..\n" );
|
||||
#endif /*DEBUG*/
|
||||
error = heif_context_encode_image( heif->ctx,
|
||||
heif->img, heif->encoder, options, &heif->handle );
|
||||
|
||||
heif_encoding_options_free( options );
|
||||
|
||||
if( error.code ) {
|
||||
vips__heif_error( &error );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
return( result );
|
||||
if( vips_image_get_typeof( heif->image, "heif-primary" ) ) {
|
||||
int primary;
|
||||
|
||||
if( vips_image_get_int( heif->image,
|
||||
"heif-primary", &primary ) )
|
||||
return( -1 );
|
||||
|
||||
if( page == primary ) {
|
||||
error = heif_context_set_primary_image( heif->ctx,
|
||||
heif->handle );
|
||||
if( error.code ) {
|
||||
vips__heif_error( &error );
|
||||
return( -1 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( !save->strip &&
|
||||
vips_foreign_save_heif_write_metadata( heif ) )
|
||||
return( -1 );
|
||||
|
||||
VIPS_FREEF( heif_image_handle_release, heif->handle );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* vips_heifsave_target: (method)
|
||||
* @in: image to save
|
||||
* @target: save image to this target
|
||||
* @...: %NULL-terminated list of optional named arguments
|
||||
*
|
||||
* Optional arguments:
|
||||
*
|
||||
* * @Q: %gint, quality factor
|
||||
* * @lossless: %gboolean, enable lossless encoding
|
||||
* * @compression: #VipsForeignHeifCompression, write with this compression
|
||||
* * @speed: %gint, encoding speed
|
||||
* * @subsample_mode: #VipsForeignSubsample, chroma subsampling mode
|
||||
*
|
||||
* As vips_heifsave(), but save to a target.
|
||||
*
|
||||
* See also: vips_heifsave(), vips_image_write_to_target().
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
*/
|
||||
int
|
||||
vips_heifsave_target( VipsImage *in, VipsTarget *target, ... )
|
||||
static int
|
||||
vips_foreign_save_heif_write_block( VipsRegion *region, VipsRect *area,
|
||||
void *a )
|
||||
{
|
||||
va_list ap;
|
||||
int result;
|
||||
VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) a;
|
||||
|
||||
va_start( ap, target );
|
||||
result = vips_call_split( "heifsave_target", ap, in, target );
|
||||
va_end( ap );
|
||||
int y;
|
||||
|
||||
return( result );
|
||||
#ifdef DEBUG
|
||||
printf( "vips_foreign_save_heif_write_block: y = %d\n", area->top );
|
||||
#endif /*DEBUG*/
|
||||
|
||||
/* Copy a line at a time into our output image, write each time the
|
||||
* image fills.
|
||||
*/
|
||||
for( y = 0; y < area->height; y++ ) {
|
||||
/* Y in page.
|
||||
*/
|
||||
int page = (area->top + y) / heif->page_height;
|
||||
int line = (area->top + y) % heif->page_height;
|
||||
|
||||
VipsPel *p = VIPS_REGION_ADDR( region, 0, area->top + y );
|
||||
VipsPel *q = heif->data + line * heif->stride;
|
||||
|
||||
memcpy( q, p, VIPS_IMAGE_SIZEOF_LINE( region->im ) );
|
||||
|
||||
/* Did we just write the final line? Write as a new page
|
||||
* into the output.
|
||||
*/
|
||||
if( line == heif->page_height - 1 )
|
||||
if( vips_foreign_save_heif_write_page( heif, page ) )
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
struct heif_error
|
||||
vips_foreign_save_heif_write( struct heif_context *ctx,
|
||||
const void *data, size_t length, void *userdata )
|
||||
{
|
||||
VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) userdata;
|
||||
|
||||
struct heif_error error;
|
||||
|
||||
error.code = 0;
|
||||
if( vips_target_write( heif->target, data, length ) )
|
||||
error.code = -1;
|
||||
|
||||
return( error );
|
||||
}
|
||||
|
||||
static int
|
||||
vips_foreign_save_heif_build( VipsObject *object )
|
||||
{
|
||||
VipsForeignSave *save = (VipsForeignSave *) object;
|
||||
VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object;
|
||||
|
||||
const char *filename;
|
||||
struct heif_error error;
|
||||
struct heif_writer writer;
|
||||
char *chroma;
|
||||
|
||||
if( VIPS_OBJECT_CLASS( vips_foreign_save_heif_parent_class )->
|
||||
build( object ) )
|
||||
return( -1 );
|
||||
|
||||
/* Make a copy of the image in case we modify the metadata eg. for
|
||||
* exif_update.
|
||||
*/
|
||||
if( vips_copy( save->ready, &heif->image, NULL ) )
|
||||
return( -1 );
|
||||
|
||||
/* Compression defaults to VIPS_FOREIGN_HEIF_COMPRESSION_AV1 for .avif
|
||||
* suffix.
|
||||
*/
|
||||
filename = vips_connection_filename( VIPS_CONNECTION( heif->target ) );
|
||||
if( !vips_object_argument_isset( object, "compression" ) &&
|
||||
filename &&
|
||||
vips_iscasepostfix( filename, ".avif" ) )
|
||||
heif->compression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1;
|
||||
|
||||
error = heif_context_get_encoder_for_format( heif->ctx,
|
||||
(enum heif_compression_format) heif->compression,
|
||||
&heif->encoder );
|
||||
if( error.code ) {
|
||||
if( error.code == heif_error_Unsupported_filetype )
|
||||
vips_error( "heifsave",
|
||||
"%s", _( "Unsupported compression" ) );
|
||||
else
|
||||
vips__heif_error( &error );
|
||||
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
error = heif_encoder_set_lossy_quality( heif->encoder, heif->Q );
|
||||
if( error.code ) {
|
||||
vips__heif_error( &error );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
error = heif_encoder_set_lossless( heif->encoder, heif->lossless );
|
||||
if( error.code ) {
|
||||
vips__heif_error( &error );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
error = heif_encoder_set_parameter_integer( heif->encoder,
|
||||
"speed", heif->speed );
|
||||
if( error.code &&
|
||||
error.subcode != heif_suberror_Unsupported_parameter ) {
|
||||
vips__heif_error( &error );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
chroma = heif->subsample_mode == VIPS_FOREIGN_SUBSAMPLE_OFF ||
|
||||
( heif->subsample_mode == VIPS_FOREIGN_SUBSAMPLE_AUTO &&
|
||||
heif->Q >= 90 ) ? "444" : "420";
|
||||
error = heif_encoder_set_parameter_string( heif->encoder,
|
||||
"chroma", chroma );
|
||||
if( error.code &&
|
||||
error.subcode != heif_suberror_Unsupported_parameter ) {
|
||||
vips__heif_error( &error );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
/* TODO .. support extra per-encoder params with
|
||||
* heif_encoder_list_parameters().
|
||||
*/
|
||||
|
||||
heif->page_width = heif->image->Xsize;
|
||||
heif->page_height = vips_image_get_page_height( heif->image );
|
||||
heif->n_pages = heif->image->Ysize / heif->page_height;
|
||||
|
||||
/* Make a heif image the size of a page. We send sink_disc() output
|
||||
* here and write a frame each time it fills.
|
||||
*/
|
||||
#ifdef DEBUG
|
||||
printf( "vips_foreign_save_heif_build:\n" );
|
||||
printf( "\twidth = %d\n", heif->page_width );
|
||||
printf( "\theight = %d\n", heif->page_height );
|
||||
printf( "\talpha = %d\n", vips_image_hasalpha( heif->image ) );
|
||||
#endif /*DEBUG*/
|
||||
error = heif_image_create( heif->page_width, heif->page_height,
|
||||
heif_colorspace_RGB,
|
||||
vips_image_hasalpha( heif->image ) ?
|
||||
heif_chroma_interleaved_RGBA :
|
||||
heif_chroma_interleaved_RGB,
|
||||
&heif->img );
|
||||
if( error.code ) {
|
||||
vips__heif_error( &error );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
error = heif_image_add_plane( heif->img, heif_channel_interleaved,
|
||||
heif->page_width, heif->page_height,
|
||||
vips_image_hasalpha( heif->image ) ? 32 : 24 );
|
||||
if( error.code ) {
|
||||
vips__heif_error( &error );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
vips__heif_image_print( heif->img );
|
||||
#endif /*DEBUG*/
|
||||
|
||||
heif->data = heif_image_get_plane( heif->img,
|
||||
heif_channel_interleaved, &heif->stride );
|
||||
|
||||
/* Write data.
|
||||
*/
|
||||
if( vips_sink_disc( heif->image,
|
||||
vips_foreign_save_heif_write_block, heif ) )
|
||||
return( -1 );
|
||||
|
||||
/* This has to come right at the end :-( so there's no support for
|
||||
* incremental writes.
|
||||
*/
|
||||
writer.writer_api_version = 1;
|
||||
writer.write = vips_foreign_save_heif_write;
|
||||
error = heif_context_write( heif->ctx, &writer, heif );
|
||||
if( error.code ) {
|
||||
vips__heif_error( &error );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
vips_target_finish( heif->target );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
/* Save a bit of typing.
|
||||
*/
|
||||
#define UC VIPS_FORMAT_UCHAR
|
||||
|
||||
static int vips_heif_bandfmt[10] = {
|
||||
/* UC C US S UI I F X D DX */
|
||||
UC, UC, UC, UC, UC, UC, UC, UC, UC, UC
|
||||
};
|
||||
|
||||
static void
|
||||
vips_foreign_save_heif_class_init( VipsForeignSaveHeifClass *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_heif_dispose;
|
||||
gobject_class->set_property = vips_object_set_property;
|
||||
gobject_class->get_property = vips_object_get_property;
|
||||
|
||||
object_class->nickname = "heifsave_base";
|
||||
object_class->description = _( "save image in HEIF format" );
|
||||
object_class->build = vips_foreign_save_heif_build;
|
||||
|
||||
foreign_class->suffs = vips__heif_suffs;
|
||||
|
||||
save_class->saveable = VIPS_SAVEABLE_RGBA_ONLY;
|
||||
save_class->format_table = vips_heif_bandfmt;
|
||||
|
||||
VIPS_ARG_INT( class, "Q", 10,
|
||||
_( "Q" ),
|
||||
_( "Q factor" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSaveHeif, Q ),
|
||||
1, 100, 50 );
|
||||
|
||||
VIPS_ARG_BOOL( class, "lossless", 13,
|
||||
_( "Lossless" ),
|
||||
_( "Enable lossless compression" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSaveHeif, lossless ),
|
||||
FALSE );
|
||||
|
||||
VIPS_ARG_ENUM( class, "compression", 14,
|
||||
_( "compression" ),
|
||||
_( "Compression format" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSaveHeif, compression ),
|
||||
VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
|
||||
VIPS_FOREIGN_HEIF_COMPRESSION_HEVC );
|
||||
|
||||
VIPS_ARG_INT( class, "speed", 15,
|
||||
_( "speed" ),
|
||||
_( "CPU effort" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSaveHeif, speed ),
|
||||
0, 9, 5 );
|
||||
|
||||
VIPS_ARG_ENUM( class, "subsample_mode", 16,
|
||||
_( "Subsample mode" ),
|
||||
_( "Select chroma subsample operation mode" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSaveHeif, subsample_mode ),
|
||||
VIPS_TYPE_FOREIGN_SUBSAMPLE,
|
||||
VIPS_FOREIGN_SUBSAMPLE_AUTO );
|
||||
}
|
||||
|
||||
static void
|
||||
vips_foreign_save_heif_init( VipsForeignSaveHeif *heif )
|
||||
{
|
||||
heif->ctx = heif_context_alloc();
|
||||
heif->Q = 50;
|
||||
heif->compression = VIPS_FOREIGN_HEIF_COMPRESSION_HEVC;
|
||||
heif->speed = 5;
|
||||
heif->subsample_mode = VIPS_FOREIGN_SUBSAMPLE_AUTO;
|
||||
}
|
||||
|
||||
typedef struct _VipsForeignSaveHeifFile {
|
||||
VipsForeignSaveHeif parent_object;
|
||||
|
||||
/* Filename for save.
|
||||
*/
|
||||
char *filename;
|
||||
|
||||
} VipsForeignSaveHeifFile;
|
||||
|
||||
typedef VipsForeignSaveHeifClass VipsForeignSaveHeifFileClass;
|
||||
|
||||
G_DEFINE_TYPE( VipsForeignSaveHeifFile, vips_foreign_save_heif_file,
|
||||
vips_foreign_save_heif_get_type() );
|
||||
|
||||
static int
|
||||
vips_foreign_save_heif_file_build( VipsObject *object )
|
||||
{
|
||||
VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object;
|
||||
VipsForeignSaveHeifFile *file = (VipsForeignSaveHeifFile *) object;
|
||||
|
||||
if( !(heif->target = vips_target_new_to_file( file->filename )) )
|
||||
return( -1 );
|
||||
|
||||
if( VIPS_OBJECT_CLASS( vips_foreign_save_heif_file_parent_class )->
|
||||
build( object ) )
|
||||
return( -1 );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
static void
|
||||
vips_foreign_save_heif_file_class_init( VipsForeignSaveHeifFileClass *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 = "heifsave";
|
||||
object_class->build = vips_foreign_save_heif_file_build;
|
||||
|
||||
VIPS_ARG_STRING( class, "filename", 1,
|
||||
_( "Filename" ),
|
||||
_( "Filename to save to" ),
|
||||
VIPS_ARGUMENT_REQUIRED_INPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSaveHeifFile, filename ),
|
||||
NULL );
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
vips_foreign_save_heif_file_init( VipsForeignSaveHeifFile *file )
|
||||
{
|
||||
}
|
||||
|
||||
typedef struct _VipsForeignSaveHeifBuffer {
|
||||
VipsForeignSaveHeif parent_object;
|
||||
|
||||
/* Save to a buffer.
|
||||
*/
|
||||
VipsArea *buf;
|
||||
|
||||
} VipsForeignSaveHeifBuffer;
|
||||
|
||||
typedef VipsForeignSaveHeifClass VipsForeignSaveHeifBufferClass;
|
||||
|
||||
G_DEFINE_TYPE( VipsForeignSaveHeifBuffer, vips_foreign_save_heif_buffer,
|
||||
vips_foreign_save_heif_get_type() );
|
||||
|
||||
static int
|
||||
vips_foreign_save_heif_buffer_build( VipsObject *object )
|
||||
{
|
||||
VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object;
|
||||
VipsForeignSaveHeifBuffer *buffer =
|
||||
(VipsForeignSaveHeifBuffer *) object;
|
||||
|
||||
VipsBlob *blob;
|
||||
|
||||
if( !(heif->target = vips_target_new_to_memory()) )
|
||||
return( -1 );
|
||||
|
||||
if( VIPS_OBJECT_CLASS( vips_foreign_save_heif_buffer_parent_class )->
|
||||
build( object ) )
|
||||
return( -1 );
|
||||
|
||||
g_object_get( heif->target, "blob", &blob, NULL );
|
||||
g_object_set( buffer, "buffer", blob, NULL );
|
||||
vips_area_unref( VIPS_AREA( blob ) );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
static void
|
||||
vips_foreign_save_heif_buffer_class_init(
|
||||
VipsForeignSaveHeifBufferClass *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 = "heifsave_buffer";
|
||||
object_class->build = vips_foreign_save_heif_buffer_build;
|
||||
|
||||
VIPS_ARG_BOXED( class, "buffer", 1,
|
||||
_( "Buffer" ),
|
||||
_( "Buffer to save to" ),
|
||||
VIPS_ARGUMENT_REQUIRED_OUTPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSaveHeifBuffer, buf ),
|
||||
VIPS_TYPE_BLOB );
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
vips_foreign_save_heif_buffer_init( VipsForeignSaveHeifBuffer *buffer )
|
||||
{
|
||||
}
|
||||
|
||||
typedef struct _VipsForeignSaveHeifTarget {
|
||||
VipsForeignSaveHeif parent_object;
|
||||
|
||||
VipsTarget *target;
|
||||
} VipsForeignSaveHeifTarget;
|
||||
|
||||
typedef VipsForeignSaveHeifClass VipsForeignSaveHeifTargetClass;
|
||||
|
||||
G_DEFINE_TYPE( VipsForeignSaveHeifTarget, vips_foreign_save_heif_target,
|
||||
vips_foreign_save_heif_get_type() );
|
||||
|
||||
static int
|
||||
vips_foreign_save_heif_target_build( VipsObject *object )
|
||||
{
|
||||
VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object;
|
||||
VipsForeignSaveHeifTarget *target =
|
||||
(VipsForeignSaveHeifTarget *) object;
|
||||
|
||||
if( target->target ) {
|
||||
heif->target = target->target;
|
||||
g_object_ref( heif->target );
|
||||
}
|
||||
|
||||
if( VIPS_OBJECT_CLASS( vips_foreign_save_heif_target_parent_class )->
|
||||
build( object ) )
|
||||
return( -1 );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
static void
|
||||
vips_foreign_save_heif_target_class_init(
|
||||
VipsForeignSaveHeifTargetClass *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 = "heifsave_target";
|
||||
object_class->build = vips_foreign_save_heif_target_build;
|
||||
|
||||
VIPS_ARG_OBJECT( class, "target", 1,
|
||||
_( "Target" ),
|
||||
_( "Target to save to" ),
|
||||
VIPS_ARGUMENT_REQUIRED_INPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSaveHeifTarget, target ),
|
||||
VIPS_TYPE_TARGET );
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
vips_foreign_save_heif_target_init( VipsForeignSaveHeifTarget *target )
|
||||
{
|
||||
}
|
||||
|
||||
#endif /*HAVE_HEIF_ENCODER*/
|
||||
|
||||
/* The C API wrappers are defined in foreign.c.
|
||||
*/
|
||||
|
@ -1,918 +0,0 @@
|
||||
/* 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 );
|
||||
/* Read error, or unexpected end of input.
|
||||
*/
|
||||
if( bytes_read <= 0 )
|
||||
return( -1 );
|
||||
jxl->bytes_in_buffer = bytes_read + bytes_remaining;
|
||||
|
||||
#ifdef DEBUG_VERBOSE
|
||||
printf( "vips_foreign_load_jxl_fill_input: %zd bytes read\n",
|
||||
bytes_read );
|
||||
#endif /*DEBUG_VERBOSE*/
|
||||
|
||||
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 ) )
|
||||
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 )
|
||||
{
|
||||
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( jxl );
|
||||
|
||||
VipsBandFormat format;
|
||||
VipsInterpretation interpretation;
|
||||
|
||||
if( jxl->info.xsize >= VIPS_MAX_COORD ||
|
||||
jxl->info.ysize >= VIPS_MAX_COORD ) {
|
||||
vips_error( class->nickname,
|
||||
"%s", _( "image size out of bounds" ) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
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_source_rewind( jxl->source ) )
|
||||
return( -1 );
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
printf( "vips_foreign_load_jxl_header: "
|
||||
"%zd byte profile\n", jxl->icc_size );
|
||||
#endif /*DEBUG*/
|
||||
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;
|
||||
}
|
||||
/* JXL_DEC_COLOR_ENCODING is always the last status signal before
|
||||
* pixel decoding starts.
|
||||
*/
|
||||
} while( status != JXL_DEC_COLOR_ENCODING );
|
||||
|
||||
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*/
|
@ -31,91 +31,890 @@
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
#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>
|
||||
|
||||
/**
|
||||
* vips_jxlload:
|
||||
* @filename: file to load
|
||||
* @out: (out): decompressed image
|
||||
* @...: %NULL-terminated list of optional named arguments
|
||||
#ifdef HAVE_LIBJXL
|
||||
|
||||
#include <jxl/decode.h>
|
||||
#include <jxl/thread_parallel_runner.h>
|
||||
|
||||
#include "pforeign.h"
|
||||
|
||||
/* TODO:
|
||||
*
|
||||
* Read a JPEG-XL image.
|
||||
* - add metadata support
|
||||
*
|
||||
* The JPEG-XL loader and saver are experimental features and may change
|
||||
* in future libvips versions.
|
||||
* - add animation support
|
||||
*
|
||||
* See also: vips_image_new_from_file().
|
||||
* - add "shrink" option to read out 8x shrunk image?
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
* - fix scRGB gamma
|
||||
*/
|
||||
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 );
|
||||
#define INPUT_BUFFER_SIZE (4096)
|
||||
|
||||
return( result );
|
||||
}
|
||||
typedef struct _VipsForeignLoadJxl {
|
||||
VipsForeignLoad parent_object;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
/* Source to load from (set by subclasses).
|
||||
*/
|
||||
blob = vips_blob_new( NULL, buf, len );
|
||||
VipsSource *source;
|
||||
|
||||
va_start( ap, out );
|
||||
result = vips_call_split( "jxlload_buffer", ap, blob, out );
|
||||
va_end( ap );
|
||||
/* Page set by user, then we translate that into shrink factor.
|
||||
*/
|
||||
int page;
|
||||
int shrink;
|
||||
|
||||
vips_area_unref( VIPS_AREA( blob ) );
|
||||
/* Base image properties.
|
||||
*/
|
||||
JxlBasicInfo info;
|
||||
JxlPixelFormat format;
|
||||
size_t icc_size;
|
||||
uint8_t *icc_data;
|
||||
|
||||
return( result );
|
||||
}
|
||||
/* Decompress state.
|
||||
*/
|
||||
void *runner;
|
||||
JxlDecoder *decoder;
|
||||
|
||||
/**
|
||||
* 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, ... )
|
||||
/* 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 )
|
||||
{
|
||||
va_list ap;
|
||||
int result;
|
||||
VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) gobject;
|
||||
|
||||
va_start( ap, out );
|
||||
result = vips_call_split( "jxlload_source", ap, source, out );
|
||||
va_end( ap );
|
||||
#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 );
|
||||
/* Read error, or unexpected end of input.
|
||||
*/
|
||||
if( bytes_read <= 0 )
|
||||
return( -1 );
|
||||
jxl->bytes_in_buffer = bytes_read + bytes_remaining;
|
||||
|
||||
#ifdef DEBUG_VERBOSE
|
||||
printf( "vips_foreign_load_jxl_fill_input: %zd bytes read\n",
|
||||
bytes_read );
|
||||
#endif /*DEBUG_VERBOSE*/
|
||||
|
||||
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 ) )
|
||||
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 )
|
||||
{
|
||||
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( jxl );
|
||||
|
||||
VipsBandFormat format;
|
||||
VipsInterpretation interpretation;
|
||||
|
||||
if( jxl->info.xsize >= VIPS_MAX_COORD ||
|
||||
jxl->info.ysize >= VIPS_MAX_COORD ) {
|
||||
vips_error( class->nickname,
|
||||
"%s", _( "image size out of bounds" ) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
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_source_rewind( jxl->source ) )
|
||||
return( -1 );
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
printf( "vips_foreign_load_jxl_header: "
|
||||
"%zd byte profile\n", jxl->icc_size );
|
||||
#endif /*DEBUG*/
|
||||
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;
|
||||
}
|
||||
/* JXL_DEC_COLOR_ENCODING is always the last status signal before
|
||||
* pixel decoding starts.
|
||||
*/
|
||||
} while( status != JXL_DEC_COLOR_ENCODING );
|
||||
|
||||
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*/
|
||||
|
||||
/* The C API wrappers are defined in foreign.c.
|
||||
*/
|
||||
|
@ -36,132 +36,658 @@
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
#include <vips/intl.h>
|
||||
|
||||
#ifdef HAVE_LIBJXL
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <vips/vips.h>
|
||||
#include <vips/internal.h>
|
||||
|
||||
/**
|
||||
* vips_jxlsave: (method)
|
||||
* @in: image to save
|
||||
* @filename: file to write to
|
||||
* @...: %NULL-terminated list of optional named arguments
|
||||
#include <jxl/encode.h>
|
||||
#include <jxl/thread_parallel_runner.h>
|
||||
|
||||
#include "pforeign.h"
|
||||
|
||||
/* TODO:
|
||||
*
|
||||
* Optional arguments:
|
||||
* - libjxl currently seems to be missing API to attach a profile
|
||||
*
|
||||
* * @tier: %gint, decode speed tier
|
||||
* * @distance: %gdouble, maximum encoding error
|
||||
* * @effort: %gint, encoding effort
|
||||
* * @lossless: %gboolean, enables lossless compression
|
||||
* * @Q: %gint, quality setting
|
||||
* - libjxl encode only works in one shot mode, so there's no way to write in
|
||||
* chunks
|
||||
*
|
||||
* Write a VIPS image to a file in JPEG-XL format.
|
||||
* - add metadata support EXIF, XMP, etc. api for this is on the way
|
||||
*
|
||||
* The JPEG-XL loader and saver are experimental features and may change
|
||||
* in future libvips versions.
|
||||
* - add animation support
|
||||
*
|
||||
* @tier sets the overall decode speed the encoder will target. Minimum is 0
|
||||
* (highest quality), and maximum is 4 (lowest quality). Default is 0.
|
||||
* - libjxl is currently missing error messages (I think)
|
||||
*
|
||||
* @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.
|
||||
* - fix scRGB gamma
|
||||
*/
|
||||
int
|
||||
vips_jxlsave( VipsImage *in, const char *filename, ... )
|
||||
|
||||
#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 )
|
||||
{
|
||||
va_list ap;
|
||||
int result;
|
||||
VipsForeignSaveJxl *jxl = (VipsForeignSaveJxl *) gobject;
|
||||
|
||||
va_start( ap, filename );
|
||||
result = vips_call_split( "jxlsave", ap, in, filename );
|
||||
va_end( ap );
|
||||
VIPS_FREEF( JxlThreadParallelRunnerDestroy, jxl->runner );
|
||||
VIPS_FREEF( JxlEncoderDestroy, jxl->encoder );
|
||||
|
||||
return( result );
|
||||
G_OBJECT_CLASS( vips_foreign_save_jxl_parent_class )->
|
||||
dispose( gobject );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, ... )
|
||||
static void
|
||||
vips_foreign_save_jxl_error( VipsForeignSaveJxl *jxl, const char *details )
|
||||
{
|
||||
va_list ap;
|
||||
VipsArea *area;
|
||||
int result;
|
||||
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( jxl );
|
||||
|
||||
area = NULL;
|
||||
/* TODO ... jxl has no way to get error messages at the moment.
|
||||
*/
|
||||
vips_error( class->nickname, "error %s", details );
|
||||
}
|
||||
|
||||
va_start( ap, len );
|
||||
result = vips_call_split( "jxlsave_buffer", ap, in, &area );
|
||||
va_end( ap );
|
||||
#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 );
|
||||
}
|
||||
|
||||
if( !result &&
|
||||
area ) {
|
||||
if( buf ) {
|
||||
*buf = area->data;
|
||||
area->free_fn = NULL;
|
||||
}
|
||||
if( len )
|
||||
*len = area->length;
|
||||
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;
|
||||
|
||||
vips_area_unref( area );
|
||||
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 );
|
||||
}
|
||||
|
||||
return( result );
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
/* Save a bit of typing.
|
||||
*/
|
||||
int
|
||||
vips_jxlsave_target( VipsImage *in, VipsTarget *target, ... )
|
||||
#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 )
|
||||
{
|
||||
va_list ap;
|
||||
int result;
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
|
||||
VipsObjectClass *object_class = (VipsObjectClass *) class;
|
||||
VipsForeignClass *foreign_class = (VipsForeignClass *) class;
|
||||
VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class;
|
||||
|
||||
va_start( ap, target );
|
||||
result = vips_call_split( "jxlsave_target", ap, in, target );
|
||||
va_end( ap );
|
||||
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 );
|
||||
|
||||
return( result );
|
||||
}
|
||||
|
||||
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_LIBJXL*/
|
||||
|
||||
/* The C API wrappers are defined in foreign.c.
|
||||
*/
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -747,6 +747,33 @@ vips_foreign_load_pdf_file_build( VipsObject *object )
|
||||
build( object ) );
|
||||
}
|
||||
|
||||
static gboolean
|
||||
vips_foreign_load_pdf_is_a_buffer( const void *buf, size_t len )
|
||||
{
|
||||
const guchar *str = (const guchar *) buf;
|
||||
|
||||
if( len >= 4 &&
|
||||
str[0] == '%' &&
|
||||
str[1] == 'P' &&
|
||||
str[2] == 'D' &&
|
||||
str[3] == 'F' )
|
||||
return( 1 );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
static gboolean
|
||||
vips_foreign_load_pdf_is_a( const char *filename )
|
||||
{
|
||||
unsigned char buf[4];
|
||||
|
||||
if( vips__get_bytes( filename, buf, 4 ) == 4 &&
|
||||
vips_foreign_load_pdf_is_a_buffer( buf, 4 ) )
|
||||
return( 1 );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
static void
|
||||
vips_foreign_load_pdf_file_class_init(
|
||||
VipsForeignLoadPdfFileClass *class )
|
||||
|
@ -1,237 +0,0 @@
|
||||
/* load PDF with libpoppler
|
||||
*
|
||||
* 7/2/16
|
||||
* - from openslideload.c
|
||||
* 12/5/16
|
||||
* - add @n ... number of pages to load
|
||||
* 23/11/16
|
||||
* - set page-height, if we can
|
||||
* 28/6/17
|
||||
* - use a much larger strip size, thanks bubba
|
||||
* 8/6/18
|
||||
* - add background param
|
||||
* 16/8/18 [kleisauke]
|
||||
* - shut down the input file as soon as we can
|
||||
* 19/9/19
|
||||
* - reopen the input if we minimised too early
|
||||
* 11/3/20
|
||||
* - move on top of VipsSource
|
||||
* 21/9/20
|
||||
* - allow dpi and scale to both be set [le0daniel]
|
||||
* 21/4/21 kleisauke
|
||||
* - move GObject part to poppler2vips.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
|
||||
*/
|
||||
|
||||
#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 <errno.h>
|
||||
|
||||
#include <vips/vips.h>
|
||||
#include <vips/buf.h>
|
||||
#include <vips/internal.h>
|
||||
|
||||
#include "pforeign.h"
|
||||
|
||||
/* Also used by the pdfium loader.
|
||||
*/
|
||||
gboolean
|
||||
vips_foreign_load_pdf_is_a_buffer( const void *buf, size_t len )
|
||||
{
|
||||
const guchar *str = (const guchar *) buf;
|
||||
|
||||
if( len >= 4 &&
|
||||
str[0] == '%' &&
|
||||
str[1] == 'P' &&
|
||||
str[2] == 'D' &&
|
||||
str[3] == 'F' )
|
||||
return( 1 );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
/* Also used by the pdfium loader.
|
||||
*/
|
||||
gboolean
|
||||
vips_foreign_load_pdf_is_a( const char *filename )
|
||||
{
|
||||
unsigned char buf[4];
|
||||
|
||||
if( vips__get_bytes( filename, buf, 4 ) == 4 &&
|
||||
vips_foreign_load_pdf_is_a_buffer( buf, 4 ) )
|
||||
return( 1 );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* vips_pdfload:
|
||||
* @filename: file to load
|
||||
* @out: (out): output image
|
||||
* @...: %NULL-terminated list of optional named arguments
|
||||
*
|
||||
* Optional arguments:
|
||||
*
|
||||
* * @page: %gint, load this page, numbered from zero
|
||||
* * @n: %gint, load this many pages
|
||||
* * @dpi: %gdouble, render at this DPI
|
||||
* * @scale: %gdouble, scale render by this factor
|
||||
* * @background: #VipsArrayDouble background colour
|
||||
*
|
||||
* Render a PDF file into a VIPS image. Rendering uses the libpoppler library
|
||||
* and should be fast.
|
||||
*
|
||||
* The output image is always RGBA --- CMYK PDFs will be
|
||||
* converted. If you need CMYK bitmaps, you should use vips_magickload()
|
||||
* instead.
|
||||
*
|
||||
* Use @page to select a page to render, numbering from zero.
|
||||
*
|
||||
* Use @n to select the number of pages to render. The default is 1. Pages are
|
||||
* rendered in a vertical column, with each individual page aligned to the
|
||||
* left. Set to -1 to mean "until the end of the document". Use vips_grid()
|
||||
* to change page layout.
|
||||
*
|
||||
* Use @dpi to set the rendering resolution. The default is 72. Additionally,
|
||||
* you can scale by setting @scale. If you set both, they combine.
|
||||
*
|
||||
* Use @background to set the background RGBA colour. The default is 255
|
||||
* (solid white), use eg. 0 for a transparent background.
|
||||
*
|
||||
* The operation fills a number of header fields with metadata, for example
|
||||
* "pdf-author". They may be useful.
|
||||
*
|
||||
* This function only reads the image header and does not render any pixel
|
||||
* data. Rendering occurs when pixels are accessed.
|
||||
*
|
||||
* See also: vips_image_new_from_file(), vips_magickload().
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
*/
|
||||
int
|
||||
vips_pdfload( const char *filename, VipsImage **out, ... )
|
||||
{
|
||||
va_list ap;
|
||||
int result;
|
||||
|
||||
va_start( ap, out );
|
||||
result = vips_call_split( "pdfload", ap, filename, out );
|
||||
va_end( ap );
|
||||
|
||||
return( result );
|
||||
}
|
||||
|
||||
/**
|
||||
* vips_pdfload_buffer:
|
||||
* @buf: (array length=len) (element-type guint8): memory area to load
|
||||
* @len: (type gsize): size of memory area
|
||||
* @out: (out): image to write
|
||||
* @...: %NULL-terminated list of optional named arguments
|
||||
*
|
||||
* Optional arguments:
|
||||
*
|
||||
* * @page: %gint, load this page, numbered from zero
|
||||
* * @n: %gint, load this many pages
|
||||
* * @dpi: %gdouble, render at this DPI
|
||||
* * @scale: %gdouble, scale render by this factor
|
||||
* * @background: #VipsArrayDouble background colour
|
||||
*
|
||||
* Read a PDF-formatted memory buffer into a VIPS image. Exactly as
|
||||
* vips_pdfload(), but read from memory.
|
||||
*
|
||||
* You must not free the buffer while @out is active. The
|
||||
* #VipsObject::postclose signal on @out is a good place to free.
|
||||
*
|
||||
* See also: vips_pdfload().
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
*/
|
||||
int
|
||||
vips_pdfload_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( "pdfload_buffer", ap, blob, out );
|
||||
va_end( ap );
|
||||
|
||||
vips_area_unref( VIPS_AREA( blob ) );
|
||||
|
||||
return( result );
|
||||
}
|
||||
|
||||
/**
|
||||
* vips_pdfload_source:
|
||||
* @source: source to load from
|
||||
* @out: (out): image to write
|
||||
* @...: %NULL-terminated list of optional named arguments
|
||||
*
|
||||
* Optional arguments:
|
||||
*
|
||||
* * @page: %gint, load this page, numbered from zero
|
||||
* * @n: %gint, load this many pages
|
||||
* * @dpi: %gdouble, render at this DPI
|
||||
* * @scale: %gdouble, scale render by this factor
|
||||
* * @background: #VipsArrayDouble background colour
|
||||
*
|
||||
* Exactly as vips_pdfload(), but read from a source.
|
||||
*
|
||||
* See also: vips_pdfload()
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
*/
|
||||
int
|
||||
vips_pdfload_source( VipsSource *source, VipsImage **out, ... )
|
||||
{
|
||||
va_list ap;
|
||||
int result;
|
||||
|
||||
va_start( ap, out );
|
||||
result = vips_call_split( "pdfload_source", ap, source, out );
|
||||
va_end( ap );
|
||||
|
||||
return( result );
|
||||
}
|
||||
|
@ -215,9 +215,6 @@ int vips__webp_write_target( VipsImage *image, VipsTarget *target,
|
||||
gboolean min_size, int kmin, int kmax,
|
||||
gboolean strip, const char *profile );
|
||||
|
||||
gboolean vips_foreign_load_pdf_is_a_buffer( const void *buf, size_t len );
|
||||
gboolean vips_foreign_load_pdf_is_a( const char *filename );
|
||||
|
||||
int vips__quantise_image( VipsImage *in,
|
||||
VipsImage **index_out, VipsImage **palette_out,
|
||||
int colours, int Q, double dither );
|
||||
|
@ -641,6 +641,33 @@ vips_foreign_load_pdf_file_build( VipsObject *object )
|
||||
build( object ) );
|
||||
}
|
||||
|
||||
static gboolean
|
||||
vips_foreign_load_pdf_is_a_buffer( const void *buf, size_t len )
|
||||
{
|
||||
const guchar *str = (const guchar *) buf;
|
||||
|
||||
if( len >= 4 &&
|
||||
str[0] == '%' &&
|
||||
str[1] == 'P' &&
|
||||
str[2] == 'D' &&
|
||||
str[3] == 'F' )
|
||||
return( 1 );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
static gboolean
|
||||
vips_foreign_load_pdf_is_a( const char *filename )
|
||||
{
|
||||
unsigned char buf[4];
|
||||
|
||||
if( vips__get_bytes( filename, buf, 4 ) == 4 &&
|
||||
vips_foreign_load_pdf_is_a_buffer( buf, 4 ) )
|
||||
return( 1 );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
static void
|
||||
vips_foreign_load_pdf_file_class_init(
|
||||
VipsForeignLoadPdfFileClass *class )
|
||||
@ -809,3 +836,6 @@ vips_foreign_load_pdf_source_init( VipsForeignLoadPdfSource *source )
|
||||
}
|
||||
|
||||
#endif /*defined(HAVE_POPPLER)*/
|
||||
|
||||
/* The C API wrappers are defined in foreign.c.
|
||||
*/
|
@ -1,719 +0,0 @@
|
||||
/* save to heif
|
||||
*
|
||||
* 5/7/18
|
||||
* - from niftisave.c
|
||||
* 3/7/19 [lovell]
|
||||
* - add "compression" option
|
||||
* 1/9/19 [meyermarcel]
|
||||
* - save alpha when necessary
|
||||
* 15/3/20
|
||||
* - revise for new VipsTarget API
|
||||
* 14/2/21 kleisauke
|
||||
* - include GObject part from heifsave.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>
|
||||
|
||||
#ifdef HAVE_HEIF_ENCODER
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <vips/vips.h>
|
||||
#include <vips/internal.h>
|
||||
|
||||
#include "pforeign.h"
|
||||
|
||||
#include <libheif/heif.h>
|
||||
|
||||
typedef struct _VipsForeignSaveHeif {
|
||||
VipsForeignSave parent_object;
|
||||
|
||||
/* Where to write (set by subclasses).
|
||||
*/
|
||||
VipsTarget *target;
|
||||
|
||||
/* Coding quality factor (1-100).
|
||||
*/
|
||||
int Q;
|
||||
|
||||
/* Lossless compression.
|
||||
*/
|
||||
gboolean lossless;
|
||||
|
||||
/* Compression format
|
||||
*/
|
||||
VipsForeignHeifCompression compression;
|
||||
|
||||
/* CPU effort (0-8).
|
||||
*/
|
||||
int speed;
|
||||
|
||||
/* Chroma subsampling.
|
||||
*/
|
||||
VipsForeignSubsample subsample_mode;
|
||||
|
||||
/* The image we save. This is a copy of save->ready since we need to
|
||||
* be able to update the metadata.
|
||||
*/
|
||||
VipsImage *image;
|
||||
|
||||
int page_width;
|
||||
int page_height;
|
||||
int n_pages;
|
||||
|
||||
struct heif_context *ctx;
|
||||
struct heif_encoder *encoder;
|
||||
|
||||
/* The current page we are writing.
|
||||
*/
|
||||
struct heif_image_handle *handle;
|
||||
|
||||
/* The current page in memory which we build as we scan down the
|
||||
* image.
|
||||
*/
|
||||
struct heif_image *img;
|
||||
|
||||
/* The libheif memory area we fill with pixels from the libvips
|
||||
* pipe.
|
||||
*/
|
||||
uint8_t *data;
|
||||
int stride;
|
||||
|
||||
} VipsForeignSaveHeif;
|
||||
|
||||
typedef VipsForeignSaveClass VipsForeignSaveHeifClass;
|
||||
|
||||
/* Defined in heif2vips.c
|
||||
*/
|
||||
void vips__heif_error( struct heif_error *error );
|
||||
void vips__heif_image_print( struct heif_image *img );
|
||||
|
||||
G_DEFINE_ABSTRACT_TYPE( VipsForeignSaveHeif, vips_foreign_save_heif,
|
||||
VIPS_TYPE_FOREIGN_SAVE );
|
||||
|
||||
static void
|
||||
vips_foreign_save_heif_dispose( GObject *gobject )
|
||||
{
|
||||
VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) gobject;
|
||||
|
||||
VIPS_UNREF( heif->target );
|
||||
VIPS_UNREF( heif->image );
|
||||
VIPS_FREEF( heif_image_release, heif->img );
|
||||
VIPS_FREEF( heif_image_handle_release, heif->handle );
|
||||
VIPS_FREEF( heif_encoder_release, heif->encoder );
|
||||
VIPS_FREEF( heif_context_free, heif->ctx );
|
||||
|
||||
G_OBJECT_CLASS( vips_foreign_save_heif_parent_class )->
|
||||
dispose( gobject );
|
||||
}
|
||||
|
||||
typedef struct heif_error (*libheif_metadata_fn)( struct heif_context *,
|
||||
const struct heif_image_handle *,
|
||||
const void *, int );
|
||||
|
||||
struct _VipsForeignSaveHeifMetadata {
|
||||
const char *name;
|
||||
libheif_metadata_fn saver;
|
||||
} libheif_metadata[] = {
|
||||
{ VIPS_META_EXIF_NAME, heif_context_add_exif_metadata },
|
||||
{ VIPS_META_XMP_NAME, heif_context_add_XMP_metadata }
|
||||
};
|
||||
|
||||
static int
|
||||
vips_foreign_save_heif_write_metadata( VipsForeignSaveHeif *heif )
|
||||
{
|
||||
int i;
|
||||
struct heif_error error;
|
||||
|
||||
/* Rebuild exif from tags, if we'll be saving it.
|
||||
*/
|
||||
if( vips_image_get_typeof( heif->image, VIPS_META_EXIF_NAME ) )
|
||||
if( vips__exif_update( heif->image ) )
|
||||
return( -1 );
|
||||
|
||||
for( i = 0; i < VIPS_NUMBER( libheif_metadata ); i++ )
|
||||
if( vips_image_get_typeof( heif->image,
|
||||
libheif_metadata[i].name ) ) {
|
||||
const void *data;
|
||||
size_t length;
|
||||
|
||||
#ifdef DEBUG
|
||||
printf( "attaching %s ..\n",
|
||||
libheif_metadata[i].name );
|
||||
#endif /*DEBUG*/
|
||||
|
||||
if( vips_image_get_blob( heif->image,
|
||||
libheif_metadata[i].name, &data, &length ) )
|
||||
return( -1 );
|
||||
|
||||
error = libheif_metadata[i].saver( heif->ctx,
|
||||
heif->handle, data, length );
|
||||
if( error.code ) {
|
||||
vips__heif_error( &error );
|
||||
return( -1 );
|
||||
}
|
||||
}
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
static int
|
||||
vips_foreign_save_heif_write_page( VipsForeignSaveHeif *heif, int page )
|
||||
{
|
||||
VipsForeignSave *save = (VipsForeignSave *) heif;
|
||||
|
||||
struct heif_error error;
|
||||
struct heif_encoding_options *options;
|
||||
|
||||
#ifdef HAVE_HEIF_COLOR_PROFILE
|
||||
if( !save->strip &&
|
||||
vips_image_get_typeof( heif->image, VIPS_META_ICC_NAME ) ) {
|
||||
const void *data;
|
||||
size_t length;
|
||||
|
||||
#ifdef DEBUG
|
||||
printf( "attaching profile ..\n" );
|
||||
#endif /*DEBUG*/
|
||||
|
||||
if( vips_image_get_blob( heif->image,
|
||||
VIPS_META_ICC_NAME, &data, &length ) )
|
||||
return( -1 );
|
||||
|
||||
/* FIXME .. also see heif_image_set_nclx_color_profile()
|
||||
*/
|
||||
error = heif_image_set_raw_color_profile( heif->img,
|
||||
"rICC", data, length );
|
||||
if( error.code ) {
|
||||
vips__heif_error( &error );
|
||||
return( -1 );
|
||||
}
|
||||
}
|
||||
#endif /*HAVE_HEIF_COLOR_PROFILE*/
|
||||
|
||||
options = heif_encoding_options_alloc();
|
||||
if( vips_image_hasalpha( heif->image ) )
|
||||
options->save_alpha_channel = 1;
|
||||
|
||||
#ifdef DEBUG
|
||||
printf( "encoding ..\n" );
|
||||
#endif /*DEBUG*/
|
||||
error = heif_context_encode_image( heif->ctx,
|
||||
heif->img, heif->encoder, options, &heif->handle );
|
||||
|
||||
heif_encoding_options_free( options );
|
||||
|
||||
if( error.code ) {
|
||||
vips__heif_error( &error );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
if( vips_image_get_typeof( heif->image, "heif-primary" ) ) {
|
||||
int primary;
|
||||
|
||||
if( vips_image_get_int( heif->image,
|
||||
"heif-primary", &primary ) )
|
||||
return( -1 );
|
||||
|
||||
if( page == primary ) {
|
||||
error = heif_context_set_primary_image( heif->ctx,
|
||||
heif->handle );
|
||||
if( error.code ) {
|
||||
vips__heif_error( &error );
|
||||
return( -1 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( !save->strip &&
|
||||
vips_foreign_save_heif_write_metadata( heif ) )
|
||||
return( -1 );
|
||||
|
||||
VIPS_FREEF( heif_image_handle_release, heif->handle );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
static int
|
||||
vips_foreign_save_heif_write_block( VipsRegion *region, VipsRect *area,
|
||||
void *a )
|
||||
{
|
||||
VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) a;
|
||||
|
||||
int y;
|
||||
|
||||
#ifdef DEBUG
|
||||
printf( "vips_foreign_save_heif_write_block: y = %d\n", area->top );
|
||||
#endif /*DEBUG*/
|
||||
|
||||
/* Copy a line at a time into our output image, write each time the
|
||||
* image fills.
|
||||
*/
|
||||
for( y = 0; y < area->height; y++ ) {
|
||||
/* Y in page.
|
||||
*/
|
||||
int page = (area->top + y) / heif->page_height;
|
||||
int line = (area->top + y) % heif->page_height;
|
||||
|
||||
VipsPel *p = VIPS_REGION_ADDR( region, 0, area->top + y );
|
||||
VipsPel *q = heif->data + line * heif->stride;
|
||||
|
||||
memcpy( q, p, VIPS_IMAGE_SIZEOF_LINE( region->im ) );
|
||||
|
||||
/* Did we just write the final line? Write as a new page
|
||||
* into the output.
|
||||
*/
|
||||
if( line == heif->page_height - 1 )
|
||||
if( vips_foreign_save_heif_write_page( heif, page ) )
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
struct heif_error
|
||||
vips_foreign_save_heif_write( struct heif_context *ctx,
|
||||
const void *data, size_t length, void *userdata )
|
||||
{
|
||||
VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) userdata;
|
||||
|
||||
struct heif_error error;
|
||||
|
||||
error.code = 0;
|
||||
if( vips_target_write( heif->target, data, length ) )
|
||||
error.code = -1;
|
||||
|
||||
return( error );
|
||||
}
|
||||
|
||||
static int
|
||||
vips_foreign_save_heif_build( VipsObject *object )
|
||||
{
|
||||
VipsForeignSave *save = (VipsForeignSave *) object;
|
||||
VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object;
|
||||
|
||||
const char *filename;
|
||||
struct heif_error error;
|
||||
struct heif_writer writer;
|
||||
char *chroma;
|
||||
|
||||
if( VIPS_OBJECT_CLASS( vips_foreign_save_heif_parent_class )->
|
||||
build( object ) )
|
||||
return( -1 );
|
||||
|
||||
/* Make a copy of the image in case we modify the metadata eg. for
|
||||
* exif_update.
|
||||
*/
|
||||
if( vips_copy( save->ready, &heif->image, NULL ) )
|
||||
return( -1 );
|
||||
|
||||
/* Compression defaults to VIPS_FOREIGN_HEIF_COMPRESSION_AV1 for .avif
|
||||
* suffix.
|
||||
*/
|
||||
filename = vips_connection_filename( VIPS_CONNECTION( heif->target ) );
|
||||
if( !vips_object_argument_isset( object, "compression" ) &&
|
||||
filename &&
|
||||
vips_iscasepostfix( filename, ".avif" ) )
|
||||
heif->compression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1;
|
||||
|
||||
error = heif_context_get_encoder_for_format( heif->ctx,
|
||||
(enum heif_compression_format) heif->compression,
|
||||
&heif->encoder );
|
||||
if( error.code ) {
|
||||
if( error.code == heif_error_Unsupported_filetype )
|
||||
vips_error( "heifsave",
|
||||
"%s", _( "Unsupported compression" ) );
|
||||
else
|
||||
vips__heif_error( &error );
|
||||
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
error = heif_encoder_set_lossy_quality( heif->encoder, heif->Q );
|
||||
if( error.code ) {
|
||||
vips__heif_error( &error );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
error = heif_encoder_set_lossless( heif->encoder, heif->lossless );
|
||||
if( error.code ) {
|
||||
vips__heif_error( &error );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
error = heif_encoder_set_parameter_integer( heif->encoder,
|
||||
"speed", heif->speed );
|
||||
if( error.code &&
|
||||
error.subcode != heif_suberror_Unsupported_parameter ) {
|
||||
vips__heif_error( &error );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
chroma = heif->subsample_mode == VIPS_FOREIGN_SUBSAMPLE_OFF ||
|
||||
( heif->subsample_mode == VIPS_FOREIGN_SUBSAMPLE_AUTO &&
|
||||
heif->Q >= 90 ) ? "444" : "420";
|
||||
error = heif_encoder_set_parameter_string( heif->encoder,
|
||||
"chroma", chroma );
|
||||
if( error.code &&
|
||||
error.subcode != heif_suberror_Unsupported_parameter ) {
|
||||
vips__heif_error( &error );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
/* TODO .. support extra per-encoder params with
|
||||
* heif_encoder_list_parameters().
|
||||
*/
|
||||
|
||||
heif->page_width = heif->image->Xsize;
|
||||
heif->page_height = vips_image_get_page_height( heif->image );
|
||||
heif->n_pages = heif->image->Ysize / heif->page_height;
|
||||
|
||||
/* Make a heif image the size of a page. We send sink_disc() output
|
||||
* here and write a frame each time it fills.
|
||||
*/
|
||||
#ifdef DEBUG
|
||||
printf( "vips_foreign_save_heif_build:\n" );
|
||||
printf( "\twidth = %d\n", heif->page_width );
|
||||
printf( "\theight = %d\n", heif->page_height );
|
||||
printf( "\talpha = %d\n", vips_image_hasalpha( heif->image ) );
|
||||
#endif /*DEBUG*/
|
||||
error = heif_image_create( heif->page_width, heif->page_height,
|
||||
heif_colorspace_RGB,
|
||||
vips_image_hasalpha( heif->image ) ?
|
||||
heif_chroma_interleaved_RGBA :
|
||||
heif_chroma_interleaved_RGB,
|
||||
&heif->img );
|
||||
if( error.code ) {
|
||||
vips__heif_error( &error );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
error = heif_image_add_plane( heif->img, heif_channel_interleaved,
|
||||
heif->page_width, heif->page_height,
|
||||
vips_image_hasalpha( heif->image ) ? 32 : 24 );
|
||||
if( error.code ) {
|
||||
vips__heif_error( &error );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
vips__heif_image_print( heif->img );
|
||||
#endif /*DEBUG*/
|
||||
|
||||
heif->data = heif_image_get_plane( heif->img,
|
||||
heif_channel_interleaved, &heif->stride );
|
||||
|
||||
/* Write data.
|
||||
*/
|
||||
if( vips_sink_disc( heif->image,
|
||||
vips_foreign_save_heif_write_block, heif ) )
|
||||
return( -1 );
|
||||
|
||||
/* This has to come right at the end :-( so there's no support for
|
||||
* incremental writes.
|
||||
*/
|
||||
writer.writer_api_version = 1;
|
||||
writer.write = vips_foreign_save_heif_write;
|
||||
error = heif_context_write( heif->ctx, &writer, heif );
|
||||
if( error.code ) {
|
||||
vips__heif_error( &error );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
vips_target_finish( heif->target );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
/* Save a bit of typing.
|
||||
*/
|
||||
#define UC VIPS_FORMAT_UCHAR
|
||||
|
||||
static int vips_heif_bandfmt[10] = {
|
||||
/* UC C US S UI I F X D DX */
|
||||
UC, UC, UC, UC, UC, UC, UC, UC, UC, UC
|
||||
};
|
||||
|
||||
static void
|
||||
vips_foreign_save_heif_class_init( VipsForeignSaveHeifClass *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_heif_dispose;
|
||||
gobject_class->set_property = vips_object_set_property;
|
||||
gobject_class->get_property = vips_object_get_property;
|
||||
|
||||
object_class->nickname = "heifsave_base";
|
||||
object_class->description = _( "save image in HEIF format" );
|
||||
object_class->build = vips_foreign_save_heif_build;
|
||||
|
||||
foreign_class->suffs = vips__heif_suffs;
|
||||
|
||||
save_class->saveable = VIPS_SAVEABLE_RGBA_ONLY;
|
||||
save_class->format_table = vips_heif_bandfmt;
|
||||
|
||||
VIPS_ARG_INT( class, "Q", 10,
|
||||
_( "Q" ),
|
||||
_( "Q factor" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSaveHeif, Q ),
|
||||
1, 100, 50 );
|
||||
|
||||
VIPS_ARG_BOOL( class, "lossless", 13,
|
||||
_( "Lossless" ),
|
||||
_( "Enable lossless compression" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSaveHeif, lossless ),
|
||||
FALSE );
|
||||
|
||||
VIPS_ARG_ENUM( class, "compression", 14,
|
||||
_( "compression" ),
|
||||
_( "Compression format" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSaveHeif, compression ),
|
||||
VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
|
||||
VIPS_FOREIGN_HEIF_COMPRESSION_HEVC );
|
||||
|
||||
VIPS_ARG_INT( class, "speed", 15,
|
||||
_( "speed" ),
|
||||
_( "CPU effort" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSaveHeif, speed ),
|
||||
0, 9, 5 );
|
||||
|
||||
VIPS_ARG_ENUM( class, "subsample_mode", 16,
|
||||
_( "Subsample mode" ),
|
||||
_( "Select chroma subsample operation mode" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSaveHeif, subsample_mode ),
|
||||
VIPS_TYPE_FOREIGN_SUBSAMPLE,
|
||||
VIPS_FOREIGN_SUBSAMPLE_AUTO );
|
||||
}
|
||||
|
||||
static void
|
||||
vips_foreign_save_heif_init( VipsForeignSaveHeif *heif )
|
||||
{
|
||||
heif->ctx = heif_context_alloc();
|
||||
heif->Q = 50;
|
||||
heif->compression = VIPS_FOREIGN_HEIF_COMPRESSION_HEVC;
|
||||
heif->speed = 5;
|
||||
heif->subsample_mode = VIPS_FOREIGN_SUBSAMPLE_AUTO;
|
||||
}
|
||||
|
||||
typedef struct _VipsForeignSaveHeifFile {
|
||||
VipsForeignSaveHeif parent_object;
|
||||
|
||||
/* Filename for save.
|
||||
*/
|
||||
char *filename;
|
||||
|
||||
} VipsForeignSaveHeifFile;
|
||||
|
||||
typedef VipsForeignSaveHeifClass VipsForeignSaveHeifFileClass;
|
||||
|
||||
G_DEFINE_TYPE( VipsForeignSaveHeifFile, vips_foreign_save_heif_file,
|
||||
vips_foreign_save_heif_get_type() );
|
||||
|
||||
static int
|
||||
vips_foreign_save_heif_file_build( VipsObject *object )
|
||||
{
|
||||
VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object;
|
||||
VipsForeignSaveHeifFile *file = (VipsForeignSaveHeifFile *) object;
|
||||
|
||||
if( !(heif->target = vips_target_new_to_file( file->filename )) )
|
||||
return( -1 );
|
||||
|
||||
if( VIPS_OBJECT_CLASS( vips_foreign_save_heif_file_parent_class )->
|
||||
build( object ) )
|
||||
return( -1 );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
static void
|
||||
vips_foreign_save_heif_file_class_init( VipsForeignSaveHeifFileClass *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 = "heifsave";
|
||||
object_class->build = vips_foreign_save_heif_file_build;
|
||||
|
||||
VIPS_ARG_STRING( class, "filename", 1,
|
||||
_( "Filename" ),
|
||||
_( "Filename to save to" ),
|
||||
VIPS_ARGUMENT_REQUIRED_INPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSaveHeifFile, filename ),
|
||||
NULL );
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
vips_foreign_save_heif_file_init( VipsForeignSaveHeifFile *file )
|
||||
{
|
||||
}
|
||||
|
||||
typedef struct _VipsForeignSaveHeifBuffer {
|
||||
VipsForeignSaveHeif parent_object;
|
||||
|
||||
/* Save to a buffer.
|
||||
*/
|
||||
VipsArea *buf;
|
||||
|
||||
} VipsForeignSaveHeifBuffer;
|
||||
|
||||
typedef VipsForeignSaveHeifClass VipsForeignSaveHeifBufferClass;
|
||||
|
||||
G_DEFINE_TYPE( VipsForeignSaveHeifBuffer, vips_foreign_save_heif_buffer,
|
||||
vips_foreign_save_heif_get_type() );
|
||||
|
||||
static int
|
||||
vips_foreign_save_heif_buffer_build( VipsObject *object )
|
||||
{
|
||||
VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object;
|
||||
VipsForeignSaveHeifBuffer *buffer =
|
||||
(VipsForeignSaveHeifBuffer *) object;
|
||||
|
||||
VipsBlob *blob;
|
||||
|
||||
if( !(heif->target = vips_target_new_to_memory()) )
|
||||
return( -1 );
|
||||
|
||||
if( VIPS_OBJECT_CLASS( vips_foreign_save_heif_buffer_parent_class )->
|
||||
build( object ) )
|
||||
return( -1 );
|
||||
|
||||
g_object_get( heif->target, "blob", &blob, NULL );
|
||||
g_object_set( buffer, "buffer", blob, NULL );
|
||||
vips_area_unref( VIPS_AREA( blob ) );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
static void
|
||||
vips_foreign_save_heif_buffer_class_init(
|
||||
VipsForeignSaveHeifBufferClass *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 = "heifsave_buffer";
|
||||
object_class->build = vips_foreign_save_heif_buffer_build;
|
||||
|
||||
VIPS_ARG_BOXED( class, "buffer", 1,
|
||||
_( "Buffer" ),
|
||||
_( "Buffer to save to" ),
|
||||
VIPS_ARGUMENT_REQUIRED_OUTPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSaveHeifBuffer, buf ),
|
||||
VIPS_TYPE_BLOB );
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
vips_foreign_save_heif_buffer_init( VipsForeignSaveHeifBuffer *buffer )
|
||||
{
|
||||
}
|
||||
|
||||
typedef struct _VipsForeignSaveHeifTarget {
|
||||
VipsForeignSaveHeif parent_object;
|
||||
|
||||
VipsTarget *target;
|
||||
} VipsForeignSaveHeifTarget;
|
||||
|
||||
typedef VipsForeignSaveHeifClass VipsForeignSaveHeifTargetClass;
|
||||
|
||||
G_DEFINE_TYPE( VipsForeignSaveHeifTarget, vips_foreign_save_heif_target,
|
||||
vips_foreign_save_heif_get_type() );
|
||||
|
||||
static int
|
||||
vips_foreign_save_heif_target_build( VipsObject *object )
|
||||
{
|
||||
VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object;
|
||||
VipsForeignSaveHeifTarget *target =
|
||||
(VipsForeignSaveHeifTarget *) object;
|
||||
|
||||
if( target->target ) {
|
||||
heif->target = target->target;
|
||||
g_object_ref( heif->target );
|
||||
}
|
||||
|
||||
if( VIPS_OBJECT_CLASS( vips_foreign_save_heif_target_parent_class )->
|
||||
build( object ) )
|
||||
return( -1 );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
static void
|
||||
vips_foreign_save_heif_target_class_init(
|
||||
VipsForeignSaveHeifTargetClass *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 = "heifsave_target";
|
||||
object_class->build = vips_foreign_save_heif_target_build;
|
||||
|
||||
VIPS_ARG_OBJECT( class, "target", 1,
|
||||
_( "Target" ),
|
||||
_( "Target to save to" ),
|
||||
VIPS_ARGUMENT_REQUIRED_INPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSaveHeifTarget, target ),
|
||||
VIPS_TYPE_TARGET );
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
vips_foreign_save_heif_target_init( VipsForeignSaveHeifTarget *target )
|
||||
{
|
||||
}
|
||||
|
||||
#endif /*HAVE_HEIF_ENCODER*/
|
@ -1,695 +0,0 @@
|
||||
/* 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_LIBJXL*/
|
Loading…
x
Reference in New Issue
Block a user