add dzsave to a target (#2718)

* start adding dzsave to a target

made a thing for gsf to write to a target

* fix stray tabs in dzsave

* fix dzsave write to "."

Early versions of libgsf did not support writing to ".", so we had an ugly
workaround, but this should now be OK.

Fixing this ought to make write to target simple

* dzsave_target compiles

no idea if it works though

* seems to work now

creates stray 0 length files though, very odd

* fix stray files from dzsave

next: save to buffer is returning null

* fix buffer flush in dzsave

all tests pass!

* update changelog
This commit is contained in:
John Cupitt 2022-05-20 18:38:46 +01:00 committed by GitHub
parent f8003bda67
commit a9d64bea54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 364 additions and 260 deletions

View File

@ -1,7 +1,7 @@
21/11/21 started 8.13
- configure fails for requested but unmet dependencies [remicollet]
- add support for another quantiser [DarthSim]
- add "extend", "background" and "premultiplied" to mapim to fix edge
- add "extend", "background" and "premultiplied" to vips_mapim() to fix edge
antialiasing [GavinJoyce]
- add support for HDR HEIC and AVIF images
- add vips_spngsave()
@ -16,8 +16,9 @@
- add "maxerror" to gifsave [dloebl]
- update libnsgif API [tlsa]
- deprecate "properties" option to dzsave (now always on)
- add vips_dzsave_buffer()
- always set the min stack size for pthreads, if we can
- add "fail_on" to thumbnail
- add "fail-on" to thumbnail
- add "gap" option to vips_reduce[hv]() and vips_resize() [kleisauke]
- add "ceil" option to vips_shrink() [kleisauke]
- quality improvements for image resizing [kleisauke]

View File

@ -94,6 +94,8 @@
* 29/3/22
* - always write a properties file
* - add .szi as a registered suffix
* 9/5/22
* - add dzsave_target
*/
/*
@ -188,6 +190,99 @@
#include <gsf/gsf.h>
#pragma GCC diagnostic pop
/* A GSF output object that can write to a VipsTarget.
*/
typedef struct _GsfOutputTarget {
GsfOutput output;
VipsTarget *target;
} GsfOutputTarget;
typedef struct {
GsfOutputClass output_class;
} GsfOutputTargetClass;
G_DEFINE_TYPE( GsfOutputTarget, gsf_output_target, GSF_OUTPUT_TYPE );
static gboolean
gsf_output_target_close( GsfOutput *output )
{
GsfOutputTarget *output_target = (GsfOutputTarget *) output;
if( output_target->target ) {
vips_target_finish( output_target->target );
VIPS_UNREF( output_target->target );
return( TRUE );
}
return( FALSE );
}
static void
gsf_output_target_finalize( GObject *obj )
{
GObjectClass *parent_class;
GsfOutputTarget *output_target = (GsfOutputTarget *) obj;
gsf_output_target_close( GSF_OUTPUT( output_target ) );
parent_class = g_type_class_peek( GSF_OUTPUT_TYPE );
parent_class->finalize( obj );
}
static gboolean
gsf_output_target_write( GsfOutput *output,
size_t num_bytes, guint8 const *buffer )
{
GsfOutputTarget *output_target = (GsfOutputTarget *) output;
if( vips_target_write( output_target->target, buffer, num_bytes ) )
return( FALSE );
return( TRUE );
}
static gboolean
gsf_output_target_seek( GsfOutput *output, gsf_off_t offset, GSeekType whence )
{
/* No seek needed.
*/
return FALSE;
}
static void
gsf_output_target_init( GsfOutputTarget *output )
{
}
static void
gsf_output_target_class_init( GsfOutputTargetClass *class )
{
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
GsfOutputClass *output_class = GSF_OUTPUT_CLASS( class );
gobject_class->finalize = gsf_output_target_finalize;
output_class->Close = gsf_output_target_close;
output_class->Write = gsf_output_target_write;
output_class->Seek = gsf_output_target_seek;
}
static GsfOutput *
gsf_output_target_new( VipsTarget *target )
{
GsfOutputTarget *output;
output = g_object_new( gsf_output_target_get_type(), NULL );
output->target = target;
g_object_ref( target );
return( GSF_OUTPUT( output ) );
}
/* Simple wrapper around libgsf.
*
* We need to be able to do scattered writes to structured files. So while
@ -471,6 +566,14 @@ struct _Layer {
struct _VipsForeignSaveDz {
VipsForeignSave parent_object;
/* The target we are writing to. This is set by our subclasses.
*/
VipsTarget *target;
/* Alternatively, the filename, for filesystem output.
*/
char *filename;
char *suffix;
int overlap;
int tile_size;
@ -541,10 +644,6 @@ struct _VipsForeignSaveDz {
*/
char *dirname;
/* For DZ save, we have to write to a temp dir. Track the name here.
*/
char *tempdir;
/* The root directory name ... $basename with perhaps some extra
* stuff, eg. $(basename)_files, etc.
*/
@ -572,21 +671,6 @@ typedef VipsForeignSaveClass VipsForeignSaveDzClass;
G_DEFINE_ABSTRACT_TYPE( VipsForeignSaveDz, vips_foreign_save_dz,
VIPS_TYPE_FOREIGN_SAVE );
/* ZIP and SZI are both written as zip files.
*/
static gboolean
iszip( VipsForeignDzContainer container )
{
switch( container ) {
case VIPS_FOREIGN_DZ_CONTAINER_ZIP:
case VIPS_FOREIGN_DZ_CONTAINER_SZI:
return( TRUE );
default:
return( FALSE );
}
}
#define VIPS_ZIP_FIXED_LH_SIZE (30 + 29)
#define VIPS_ZIP_FIXED_CD_SIZE (46 + 9)
#define VIPS_ZIP_EOCD_SIZE 22
@ -705,12 +789,14 @@ vips_foreign_save_dz_dispose( GObject *gobject )
{
VipsForeignSaveDz *dz = (VipsForeignSaveDz *) gobject;
VIPS_UNREF( dz->target );
VIPS_FREEF( layer_free, dz->layer );
VIPS_FREEF( vips_gsf_tree_close, dz->tree );
VIPS_FREEF( g_object_unref, dz->out );
VIPS_FREE( dz->basename );
VIPS_FREE( dz->dirname );
VIPS_FREE( dz->tempdir );
VIPS_FREE( dz->root_name );
VIPS_FREE( dz->file_suffix );
@ -2022,7 +2108,9 @@ vips_foreign_save_dz_build( VipsObject *object )
VipsForeignSave *save = (VipsForeignSave *) object;
VipsForeignSaveDz *dz = (VipsForeignSaveDz *) object;
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( dz );
VipsRect real_pixels;
char *p;
/* Google, zoomify and iiif default to zero overlap, ".jpg".
*/
@ -2186,6 +2274,52 @@ vips_foreign_save_dz_build( VipsObject *object )
dz->tile_step );
#endif /*DEBUG*/
/* Init basename and dirname from the associated filesystem names, if
* we can.
*/
{
const char *filename = dz->filename ?
dz->filename :
vips_connection_filename( VIPS_CONNECTION( dz->target ) );
if( !vips_object_argument_isset( object, "basename" ) ) {
if( filename )
dz->basename = g_path_get_basename( filename );
else
dz->basename = g_strdup( "untitled" );
}
if( !vips_object_argument_isset( object, "dirname" ) ) {
if( filename )
dz->dirname = g_path_get_dirname( filename );
}
}
/* Remove any [options] from basename.
*/
if( (p = (char *) vips__find_rightmost_brackets( dz->basename )) )
*p = '\0';
/* If we're writing thing.zip or thing.szi, default to zip
* container.
*/
if( (p = strrchr( dz->basename, '.' )) ) {
if( !vips_object_argument_isset( object, "container" ) ) {
if( g_ascii_strcasecmp( p + 1, "zip" ) == 0 )
dz->container = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
if( g_ascii_strcasecmp( p + 1, "szi" ) == 0 )
dz->container = VIPS_FOREIGN_DZ_CONTAINER_SZI;
}
/* Remove any legal suffix. We don't remove all suffixes
* since we might be writing to a dirname with a dot in.
*/
if( g_ascii_strcasecmp( p + 1, "zip" ) == 0 ||
g_ascii_strcasecmp( p + 1, "szi" ) == 0 ||
g_ascii_strcasecmp( p + 1, "dz" ) == 0 )
*p = '\0';
}
/* Build the skeleton of the image pyramid.
*/
if( !(dz->layer = pyramid_build( dz, NULL,
@ -2197,75 +2331,32 @@ vips_foreign_save_dz_build( VipsObject *object )
else
dz->root_name = g_strdup( dz->basename );
/* Drop any options from @suffix.
/* Drop any [options] from @suffix.
*/
{
char filename[VIPS_PATH_MAX];
char option_string[VIPS_PATH_MAX];
vips__filename_split8( dz->suffix, filename, option_string );
dz->file_suffix = g_strdup( filename );
}
/* If we will be renaming our temp dir to an existing directory or
* file, stop now. See vips_rename() use below.
*/
if( dz->layout == VIPS_FOREIGN_DZ_LAYOUT_DZ &&
dz->container == VIPS_FOREIGN_DZ_CONTAINER_FS &&
dz->dirname &&
vips_existsf( "%s/%s_files", dz->dirname, dz->basename ) ) {
vips_error( "dzsave",
_( "output directory %s/%s_files exists" ),
dz->dirname, dz->basename );
return( -1 );
}
dz->file_suffix = g_strdup( dz->suffix );
if( (p = (char *) vips__find_rightmost_brackets( dz->file_suffix )) )
*p = '\0';
/* Make the thing we write the tiles into.
*/
switch( dz->container ) {
case VIPS_FOREIGN_DZ_CONTAINER_FS:
if( dz->layout == VIPS_FOREIGN_DZ_LAYOUT_DZ ) {
/* For deepzoom, we have to rearrange the output
* directory after writing it, see the end of this
* function. We write to a temporary directory, then
* pull ${basename}_files and ${basename}.dzi out into
* the current directory and remove the temp. The temp
* dir must not clash with another file.
{
GsfOutput *out;
GError *error = NULL;
char name[VIPS_PATH_MAX];
/* For filesystem output of deepzoom, we write
* dirname/basename_files/ and dirname/basename.dzi, ie. the
* output does not go into a subdirectory.
*/
char name[VIPS_PATH_MAX];
int fd;
GsfOutput *out;
GError *error = NULL;
if( dz->layout == VIPS_FOREIGN_DZ_LAYOUT_DZ )
vips_snprintf( name, VIPS_PATH_MAX,
"%s", dz->dirname );
else
vips_snprintf( name, VIPS_PATH_MAX,
"%s/%s", dz->dirname, dz->basename );
vips_snprintf( name, VIPS_PATH_MAX, "%s-XXXXXX",
dz->basename );
dz->tempdir = g_build_filename( dz->dirname,
name, NULL );
if( (fd = g_mkstemp( dz->tempdir )) == -1 ) {
vips_error( class->nickname,
_( "unable to make temporary file %s" ),
dz->tempdir );
return( -1 );
}
close( fd );
g_unlink( dz->tempdir );
if( !(out = (GsfOutput *)
gsf_outfile_stdio_new( dz->tempdir,
&error )) ) {
vips_g_error( &error );
return( -1 );
}
dz->tree = vips_gsf_tree_new( out, 0 );
}
else {
GsfOutput *out;
GError *error = NULL;
char name[VIPS_PATH_MAX];
vips_snprintf( name, VIPS_PATH_MAX, "%s/%s",
dz->dirname, dz->basename );
if( !(out = (GsfOutput *)
gsf_outfile_stdio_new( name, &error )) ) {
vips_g_error( &error );
@ -2273,7 +2364,7 @@ vips_foreign_save_dz_build( VipsObject *object )
}
dz->tree = vips_gsf_tree_new( out, 0 );
}
}
break;
case VIPS_FOREIGN_DZ_CONTAINER_ZIP:
@ -2282,25 +2373,19 @@ vips_foreign_save_dz_build( VipsObject *object )
GsfOutput *zip;
GsfOutput *out2;
GError *error = NULL;
char name[VIPS_PATH_MAX];
/* Output to a file or memory?
/* We can have dzsave("x.zip", container="fs"), ie. zip output
* from write to file. Make a target if we need one.
*/
if( dz->dirname ) {
const char *suffix =
dz->container == VIPS_FOREIGN_DZ_CONTAINER_SZI ?
"szi" : "zip";
vips_snprintf( name, VIPS_PATH_MAX, "%s/%s.%s",
dz->dirname, dz->basename, suffix );
if( !(dz->out =
gsf_output_stdio_new( name, &error )) ) {
vips_g_error( &error );
if( !dz->target ) {
if( !(dz->target =
vips_target_new_to_file( dz->filename )) )
return( -1 );
}
}
else
dz->out = gsf_output_memory_new();
/* Can be memory, a file (not a directory tree), pipe, etc.
*/
dz->out = gsf_output_target_new( dz->target );
if( !(zip = (GsfOutput *)
gsf_outfile_zip_new( dz->out, &error )) ) {
@ -2377,49 +2462,15 @@ vips_foreign_save_dz_build( VipsObject *object )
write_associated( dz ) )
return( -1 );
/* This is so ugly. In earlier versions of dzsave, we wrote x.dzi and
* x_files. Now we write x/x.dzi and x/x_files to make it possible to
* create zip files.
*
* For compatibility, rearrange the directory tree.
*
* FIXME have a flag to stop this stupidity
*/
if( dz->layout == VIPS_FOREIGN_DZ_LAYOUT_DZ &&
dz->container == VIPS_FOREIGN_DZ_CONTAINER_FS ) {
char old_name[VIPS_PATH_MAX];
char new_name[VIPS_PATH_MAX];
vips_snprintf( old_name, VIPS_PATH_MAX, "%s/%s.dzi",
dz->tempdir, dz->basename );
vips_snprintf( new_name, VIPS_PATH_MAX, "%s/%s.dzi",
dz->dirname, dz->basename );
if( vips_rename( old_name, new_name ) )
return( -1 );
vips_snprintf( old_name, VIPS_PATH_MAX, "%s/%s_files",
dz->tempdir, dz->basename );
vips_snprintf( new_name, VIPS_PATH_MAX, "%s/%s_files",
dz->dirname, dz->basename );
if( vips_rename( old_name, new_name ) )
return( -1 );
if( vips_rmdirf( "%s", dz->tempdir ) )
return( -1 );
}
/* Shut down the output to flush everything.
*/
if( vips_gsf_tree_close( dz->tree ) )
return( -1 );
dz->tree = NULL;
/* If we are writing a zip to the filesystem, we must unref out to
* force it to disc.
/* unref out to force flush in gsf_output_target_close().
*/
if( iszip( dz->container ) &&
dz->dirname != NULL )
VIPS_FREEF( g_object_unref, dz->out );
VIPS_UNREF( dz->out );
return( 0 );
}
@ -2613,6 +2664,65 @@ vips_foreign_save_dz_init( VipsForeignSaveDz *dz )
dz->skip_blanks = -1;
}
typedef struct _VipsForeignSaveDzTarget {
VipsForeignSaveDz parent_object;
VipsTarget *target;
} VipsForeignSaveDzTarget;
typedef VipsForeignSaveDzClass VipsForeignSaveDzTargetClass;
G_DEFINE_TYPE( VipsForeignSaveDzTarget, vips_foreign_save_dz_target,
vips_foreign_save_dz_get_type() );
static int
vips_foreign_save_dz_target_build( VipsObject *object )
{
VipsForeignSaveDz *dz = (VipsForeignSaveDz *) object;
VipsForeignSaveDzTarget *target = (VipsForeignSaveDzTarget *) object;
dz->target = target->target;
g_object_ref( target->target );
if( VIPS_OBJECT_CLASS( vips_foreign_save_dz_target_parent_class )->
build( object ) )
return( -1 );
return( 0 );
}
static void
vips_foreign_save_dz_target_class_init( VipsForeignSaveDzTargetClass *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 = "dzsave";
object_class->description = _( "save image to deepzoom target" );
object_class->build = vips_foreign_save_dz_target_build;
VIPS_ARG_OBJECT( class, "target", 1,
_( "Target" ),
_( "Target to save to" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsForeignSaveDzTarget, target ),
VIPS_TYPE_TARGET );
}
static void
vips_foreign_save_dz_target_init( VipsForeignSaveDzTarget *target )
{
VipsForeignSaveDz *dz = (VipsForeignSaveDz *) target;
/* zip default for target output.
*/
dz->container = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
}
typedef struct _VipsForeignSaveDzFile {
VipsForeignSaveDz parent_object;
@ -2633,39 +2743,7 @@ vips_foreign_save_dz_file_build( VipsObject *object )
VipsForeignSaveDz *dz = (VipsForeignSaveDz *) object;
VipsForeignSaveDzFile *file = (VipsForeignSaveDzFile *) object;
char *p;
/* Use @filename to set the default values for dirname and basename.
*/
if( !vips_object_argument_isset( object, "basename" ) )
dz->basename = g_path_get_basename( file->filename );
if( !vips_object_argument_isset( object, "dirname" ) )
dz->dirname = g_path_get_dirname( file->filename );
/* Remove any [options] from basename.
*/
if( (p = (char *) vips__find_rightmost_brackets( dz->basename )) )
*p = '\0';
/* If we're writing thing.zip or thing.szi, default to zip
* container.
*/
if( (p = strrchr( dz->basename, '.' )) ) {
if( !vips_object_argument_isset( object, "container" ) ) {
if( g_ascii_strcasecmp( p + 1, "zip" ) == 0 )
dz->container = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
if( g_ascii_strcasecmp( p + 1, "szi" ) == 0 )
dz->container = VIPS_FOREIGN_DZ_CONTAINER_SZI;
}
/* Remove any legal suffix. We don't remove all suffixes
* since we might be writing to a dirname with a dot in.
*/
if( g_ascii_strcasecmp( p + 1, "zip" ) == 0 ||
g_ascii_strcasecmp( p + 1, "szi" ) == 0 ||
g_ascii_strcasecmp( p + 1, "dz" ) == 0 )
*p = '\0';
}
dz->filename = file->filename;
if( VIPS_OBJECT_CLASS( vips_foreign_save_dz_file_parent_class )->
build( object ) )
@ -2715,43 +2793,19 @@ static int
vips_foreign_save_dz_buffer_build( VipsObject *object )
{
VipsForeignSaveDz *dz = (VipsForeignSaveDz *) object;
VipsForeignSaveDzBuffer *buffer = (VipsForeignSaveDzBuffer *) object;
void *obuf;
size_t olen;
VipsBlob *blob;
if( !vips_object_argument_isset( object, "basename" ) )
dz->basename = g_strdup( "untitled" );
/* Leave dirname NULL to indicate memory output.
*/
if( !(dz->target = vips_target_new_to_memory()) )
return( -1 );
if( VIPS_OBJECT_CLASS( vips_foreign_save_dz_buffer_parent_class )->
build( object ) )
return( -1 );
g_assert( GSF_IS_OUTPUT_MEMORY( dz->out ) );
/* Oh dear, we can't steal gsf's memory, and blob can't unref something
* or trigger a notify. We have to copy it.
*
* Don't use tracked, we want something that can be freed with g_free.
*
* FIXME ... blob (or area?) needs to support notify or unref.
*/
olen = gsf_output_size( GSF_OUTPUT( dz->out ) );
if( !(obuf = g_try_malloc( olen )) ) {
vips_error( "vips_tracked",
_( "out of memory --- size == %dMB" ),
(int) (olen / (1024.0 * 1024.0)) );
return( -1 );
}
memcpy( obuf,
gsf_output_memory_get_bytes( GSF_OUTPUT_MEMORY( dz->out ) ),
olen );
blob = vips_blob_new( (VipsCallbackFn) vips_area_free_cb, obuf, olen );
g_object_set( object, "buffer", blob, NULL );
g_object_get( dz->target, "blob", &blob, NULL );
g_object_set( buffer, "buffer", blob, NULL );
vips_area_unref( VIPS_AREA( blob ) );
return( 0 );
@ -2949,3 +3003,46 @@ vips_dzsave_buffer( VipsImage *in, void **buf, size_t *len, ... )
return( result );
}
/**
* vips_dzsave_target: (method)
* @in: image to save
* @target: save image to this target
* @...: %NULL-terminated list of optional named arguments
*
* Optional arguments:
*
* * @basename: %gchar base part of name
* * @layout: #VipsForeignDzLayout directory layout convention
* * @suffix: %gchar suffix for tiles
* * @overlap: %gint set tile overlap
* * @tile_size: %gint set tile size
* * @background: #VipsArrayDouble background colour
* * @depth: #VipsForeignDzDepth how deep to make the pyramid
* * @centre: %gboolean centre the tiles
* * @angle: #VipsAngle rotate the image by this much
* * @container: #VipsForeignDzContainer set container type
* * @compression: %gint zip deflate compression level
* * @region_shrink: #VipsRegionShrink how to shrink each 2x2 region.
* * @skip_blanks: %gint skip tiles which are nearly equal to the background
* * @no_strip: %gboolean don't strip tiles
* * @id: %gchar id for IIIF properties
*
* As vips_dzsave(), but save to a target.
*
* See also: vips_dzsave(), vips_image_write_to_target().
*
* Returns: 0 on success, -1 on error.
*/
int
vips_dzsave_target( VipsImage *in, VipsTarget *target, ... )
{
va_list ap;
int result;
va_start( ap, target );
result = vips_call_split( "dzsave_target", ap, in, target );
va_end( ap );
return( result );
}

View File

@ -2898,6 +2898,7 @@ vips_foreign_operation_init( void )
extern GType vips_foreign_save_dz_file_get_type( void );
extern GType vips_foreign_save_dz_buffer_get_type( void );
extern GType vips_foreign_save_dz_target_get_type( void );
extern GType vips_foreign_load_webp_file_get_type( void );
extern GType vips_foreign_load_webp_buffer_get_type( void );
@ -3042,6 +3043,7 @@ vips_foreign_operation_init( void )
#ifdef HAVE_GSF
vips_foreign_save_dz_file_get_type();
vips_foreign_save_dz_buffer_get_type();
vips_foreign_save_dz_target_get_type();
#endif /*HAVE_GSF*/
#ifdef HAVE_PNG

View File

@ -402,7 +402,8 @@ vips_foreign_save_jpeg_buffer_build( VipsObject *object )
{
VipsForeignSave *save = (VipsForeignSave *) object;
VipsForeignSaveJpeg *jpeg = (VipsForeignSaveJpeg *) object;
VipsForeignSaveJpegBuffer *file = (VipsForeignSaveJpegBuffer *) object;
VipsForeignSaveJpegBuffer *buffer =
(VipsForeignSaveJpegBuffer *) object;
VipsTarget *target;
VipsBlob *blob;
@ -425,7 +426,7 @@ vips_foreign_save_jpeg_buffer_build( VipsObject *object )
}
g_object_get( target, "blob", &blob, NULL );
g_object_set( file, "buffer", blob, NULL );
g_object_set( buffer, "buffer", blob, NULL );
vips_area_unref( VIPS_AREA( blob ) );
VIPS_UNREF( target );

View File

@ -935,6 +935,9 @@ int vips_dzsave( VipsImage *in, const char *name, ... )
VIPS_API
int vips_dzsave_buffer( VipsImage *in, void **buf, size_t *len, ... )
__attribute__((sentinel));
VIPS_API
int vips_dzsave_target( VipsImage *in, VipsTarget *target, ... )
__attribute__((sentinel));
/**
* VipsForeignHeifCompression: