add dzsave_buffer

zip only ... see https://github.com/jcupitt/libvips/issues/415
This commit is contained in:
John Cupitt 2016-10-15 19:01:34 +01:00
parent c90b8be0b8
commit 156b0433d5
5 changed files with 291 additions and 71 deletions

View File

@ -1,6 +1,7 @@
13/10/16 started 8.5.0 13/10/16 started 8.5.0
- rewritten buffer system is safer and frees memory earlier - rewritten buffer system is safer and frees memory earlier
- added tiff save to buffer - added tiff save to buffer
- added dzsave save to buffer (zip only)
27/9/16 started 8.4.2 27/9/16 started 8.4.2
- small doc improvements - small doc improvements

View File

@ -67,6 +67,8 @@
* - more overlap changes to help gmaps mode * - more overlap changes to help gmaps mode
* 8/9/16 Felix Bünemann * 8/9/16 Felix Bünemann
* - move vips-properties out of subdir for gm and zoomify layouts * - move vips-properties out of subdir for gm and zoomify layouts
* 15/10/16
* - add dzsave_buffer
*/ */
/* /*
@ -557,10 +559,6 @@ struct _Layer {
struct _VipsForeignSaveDz { struct _VipsForeignSaveDz {
VipsForeignSave parent_object; VipsForeignSave parent_object;
/* Name to write to.
*/
char *name;
char *suffix; char *suffix;
int overlap; int overlap;
int tile_size; int tile_size;
@ -614,11 +612,16 @@ struct _VipsForeignSaveDz {
*/ */
VipsGsfDirectory *tree; VipsGsfDirectory *tree;
/* @name, but without a path at the start and without a suffix. /* The actual output object our zip (or whatever) is writing to.
*/
GsfOutput *out;
/* The name to save as, eg. deepzoom tiles go into ${basename}_files.
* No suffix, no path at the start.
*/ */
char *basename; char *basename;
/* @name, but just the path at the front. /* The directory we write the output to, or NULL for memory output.
*/ */
char *dirname; char *dirname;
@ -650,7 +653,7 @@ struct _VipsForeignSaveDz {
typedef VipsForeignSaveClass VipsForeignSaveDzClass; typedef VipsForeignSaveClass VipsForeignSaveDzClass;
G_DEFINE_TYPE( VipsForeignSaveDz, vips_foreign_save_dz, G_DEFINE_ABSTRACT_TYPE( VipsForeignSaveDz, vips_foreign_save_dz,
VIPS_TYPE_FOREIGN_SAVE ); VIPS_TYPE_FOREIGN_SAVE );
/* Free a pyramid. /* Free a pyramid.
@ -672,6 +675,7 @@ vips_foreign_save_dz_dispose( GObject *gobject )
VIPS_FREEF( layer_free, dz->layer ); VIPS_FREEF( layer_free, dz->layer );
VIPS_FREEF( vips_gsf_tree_free, dz->tree ); VIPS_FREEF( vips_gsf_tree_free, dz->tree );
VIPS_FREEF( g_object_unref, dz->out );
VIPS_FREE( dz->basename ); VIPS_FREE( dz->basename );
VIPS_FREE( dz->dirname ); VIPS_FREE( dz->dirname );
VIPS_FREE( dz->tempdir ); VIPS_FREE( dz->tempdir );
@ -1819,36 +1823,6 @@ vips_foreign_save_dz_build( VipsObject *object )
save->ready->Xsize, save->ready->Ysize, &real_pixels )) ) save->ready->Xsize, save->ready->Ysize, &real_pixels )) )
return( -1 ); return( -1 );
/* Drop any path stuff at the start of the output name and remove the
* suffix.
*/
{
char *p;
dz->basename = g_path_get_basename( dz->name );
if( (p = (char *) vips__find_rightmost_brackets( dz->basename )) )
*p = '\0';
if( (p = strrchr( dz->basename, '.' )) ) {
/* If we're writing to thing.zip or thing.szi, default to zip
* container.
*/
if( !vips_object_argument_isset( object, "container" ) )
if( strcasecmp( p + 1, "zip" ) == 0 ||
strcasecmp( p + 1, "szi" ) == 0 )
dz->container = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
/* Always remove .szi, .zip, .dz. We don't remove all suffixes
* since we might be writing to a dirname with a dot in.
*/
if( strcasecmp( p + 1, "zip" ) == 0 ||
strcasecmp( p + 1, "szi" ) == 0 ||
strcasecmp( p + 1, "dz" ) == 0 )
*p = '\0';
}
}
dz->dirname = g_path_get_dirname( dz->name );
if( dz->layout == VIPS_FOREIGN_DZ_LAYOUT_DZ ) if( dz->layout == VIPS_FOREIGN_DZ_LAYOUT_DZ )
dz->root_name = g_strdup_printf( "%s_files", dz->basename ); dz->root_name = g_strdup_printf( "%s_files", dz->basename );
else else
@ -1869,6 +1843,7 @@ vips_foreign_save_dz_build( VipsObject *object )
*/ */
if( dz->layout == VIPS_FOREIGN_DZ_LAYOUT_DZ && if( dz->layout == VIPS_FOREIGN_DZ_LAYOUT_DZ &&
dz->container == VIPS_FOREIGN_DZ_CONTAINER_FS && dz->container == VIPS_FOREIGN_DZ_CONTAINER_FS &&
dz->dirname &&
vips_existsf( "%s/%s_files", dz->dirname, dz->basename ) ) { vips_existsf( "%s/%s_files", dz->dirname, dz->basename ) ) {
vips_error( "dzsave", vips_error( "dzsave",
_( "output directory %s/%s_files exists" ), _( "output directory %s/%s_files exists" ),
@ -1934,31 +1909,30 @@ vips_foreign_save_dz_build( VipsObject *object )
case VIPS_FOREIGN_DZ_CONTAINER_ZIP: case VIPS_FOREIGN_DZ_CONTAINER_ZIP:
{ {
GsfOutput *out;
GsfOutput *zip; GsfOutput *zip;
GsfOutput *out2; GsfOutput *out2;
GError *error = NULL; GError *error = NULL;
char name[VIPS_PATH_MAX]; char name[VIPS_PATH_MAX];
/* This is the zip we are building. /* Output to a file or memory?
*/ */
vips_snprintf( name, VIPS_PATH_MAX, "%s/%s.zip", if( dz->dirname ) {
dz->dirname, dz->basename ); vips_snprintf( name, VIPS_PATH_MAX, "%s/%s.zip",
if( !(out = gsf_output_stdio_new( name, &error )) ) { dz->dirname, dz->basename );
vips_g_error( &error ); if( !(dz->out = gsf_output_stdio_new( name, &error )) ) {
return( -1 ); vips_g_error( &error );
return( -1 );
}
} }
else
dz->out = gsf_output_memory_new();
if( !(zip = (GsfOutput *) if( !(zip = (GsfOutput *)
gsf_outfile_zip_new( out, &error )) ) { gsf_outfile_zip_new( dz->out, &error )) ) {
vips_g_error( &error ); vips_g_error( &error );
return( -1 ); return( -1 );
} }
/* We can unref @out since @zip has a ref to it.
*/
g_object_unref( out );
/* Make the base directory inside the zip. All stuff goes into /* Make the base directory inside the zip. All stuff goes into
* this. * this.
*/ */
@ -2084,7 +2058,7 @@ vips_foreign_save_dz_class_init( VipsForeignSaveDzClass *class )
gobject_class->set_property = vips_object_set_property; gobject_class->set_property = vips_object_set_property;
gobject_class->get_property = vips_object_get_property; gobject_class->get_property = vips_object_get_property;
object_class->nickname = "dzsave"; object_class->nickname = "dzsave_base";
object_class->description = _( "save image to deep zoom format" ); object_class->description = _( "save image to deep zoom format" );
object_class->build = vips_foreign_save_dz_build; object_class->build = vips_foreign_save_dz_build;
@ -2094,11 +2068,11 @@ vips_foreign_save_dz_class_init( VipsForeignSaveDzClass *class )
save_class->format_table = bandfmt_dz; save_class->format_table = bandfmt_dz;
save_class->coding[VIPS_CODING_LABQ] = TRUE; save_class->coding[VIPS_CODING_LABQ] = TRUE;
VIPS_ARG_STRING( class, "filename", 1, VIPS_ARG_STRING( class, "basename", 2,
_( "Filename" ), _( "Base name" ),
_( "Filename to save to" ), _( "Base name to save to" ),
VIPS_ARGUMENT_REQUIRED_INPUT, VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsForeignSaveDz, name ), G_STRUCT_OFFSET( VipsForeignSaveDz, basename ),
NULL ); NULL );
VIPS_ARG_ENUM( class, "layout", 8, VIPS_ARG_ENUM( class, "layout", 8,
@ -2178,17 +2152,10 @@ vips_foreign_save_dz_class_init( VipsForeignSaveDzClass *class )
*/ */
VIPS_ARG_STRING( class, "dirname", 1, VIPS_ARG_STRING( class, "dirname", 1,
_( "Base name" ), _( "Directory name" ),
_( "Base name to save to" ), _( "Directory name to save to" ),
VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
G_STRUCT_OFFSET( VipsForeignSaveDz, name ), G_STRUCT_OFFSET( VipsForeignSaveDz, dirname ),
NULL );
VIPS_ARG_STRING( class, "basename", 1,
_( "Base name" ),
_( "Base name to save to" ),
VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
G_STRUCT_OFFSET( VipsForeignSaveDz, name ),
NULL ); NULL );
VIPS_ARG_INT( class, "tile_width", 12, VIPS_ARG_INT( class, "tile_width", 12,
@ -2221,6 +2188,178 @@ vips_foreign_save_dz_init( VipsForeignSaveDz *dz )
dz->compression = 0; dz->compression = 0;
} }
typedef struct _VipsForeignSaveDzFile {
VipsForeignSaveDz parent_object;
/* Filename for save.
*/
char *filename;
} VipsForeignSaveDzFile;
typedef VipsForeignSaveDzClass VipsForeignSaveDzFileClass;
G_DEFINE_TYPE( VipsForeignSaveDzFile, vips_foreign_save_dz_file,
vips_foreign_save_dz_get_type() );
static int
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( strcasecmp( p + 1, "zip" ) == 0 ||
strcasecmp( p + 1, "szi" ) == 0 )
dz->container = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
/* Remove any legal suffix. We don't remove all suffixes
* since we might be writing to a dirname with a dot in.
*/
if( strcasecmp( p + 1, "zip" ) == 0 ||
strcasecmp( p + 1, "szi" ) == 0 ||
strcasecmp( p + 1, "dz" ) == 0 )
*p = '\0';
}
if( VIPS_OBJECT_CLASS( vips_foreign_save_dz_file_parent_class )->
build( object ) )
return( -1 );
return( 0 );
}
static void
vips_foreign_save_dz_file_class_init( VipsForeignSaveDzFileClass *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 file" );
object_class->build = vips_foreign_save_dz_file_build;
VIPS_ARG_STRING( class, "filename", 1,
_( "Filename" ),
_( "Filename to save to" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsForeignSaveDzFile, filename ),
NULL );
}
static void
vips_foreign_save_dz_file_init( VipsForeignSaveDzFile *file )
{
}
typedef struct _VipsForeignSaveDzBuffer {
VipsForeignSaveDz parent_object;
VipsArea *buf;
} VipsForeignSaveDzBuffer;
typedef VipsForeignSaveDzClass VipsForeignSaveDzBufferClass;
G_DEFINE_TYPE( VipsForeignSaveDzBuffer, vips_foreign_save_dz_buffer,
vips_foreign_save_dz_get_type() );
static int
vips_foreign_save_dz_buffer_build( VipsObject *object )
{
VipsForeignSaveDz *dz = (VipsForeignSaveDz *) object;
void *obuf;
size_t olen;
VipsBlob *blob;
/* Memory output must always be zip.
*/
dz->container = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
if( !vips_object_argument_isset( object, "basename" ) )
dz->basename = g_strdup( "untitled" );
/* Leave dirname NULL to indicate memory output.
*/
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) g_free, obuf, olen );
g_object_set( object, "buffer", blob, NULL );
vips_area_unref( VIPS_AREA( blob ) );
return( 0 );
}
static void
vips_foreign_save_dz_buffer_class_init( VipsForeignSaveDzBufferClass *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_buffer";
object_class->description = _( "save image to dz buffer" );
object_class->build = vips_foreign_save_dz_buffer_build;
VIPS_ARG_BOXED( class, "buffer", 1,
_( "Buffer" ),
_( "Buffer to save to" ),
VIPS_ARGUMENT_REQUIRED_OUTPUT,
G_STRUCT_OFFSET( VipsForeignSaveDzBuffer, buf ),
VIPS_TYPE_BLOB );
}
static void
vips_foreign_save_dz_buffer_init( VipsForeignSaveDzBuffer *buffer )
{
}
#endif /*HAVE_GSF*/ #endif /*HAVE_GSF*/
/** /**
@ -2231,6 +2370,7 @@ vips_foreign_save_dz_init( VipsForeignSaveDz *dz )
* *
* Optional arguments: * Optional arguments:
* *
* * @basename: %gchar, base part of name
* * @layout: #VipsForeignDzLayout directory layout convention * * @layout: #VipsForeignDzLayout directory layout convention
* * @suffix: suffix for tile tiles * * @suffix: suffix for tile tiles
* * @overlap: %gint set tile overlap * * @overlap: %gint set tile overlap
@ -2248,7 +2388,10 @@ vips_foreign_save_dz_init( VipsForeignSaveDz *dz )
* *
* vips_dzsave() creates a directory called @name to hold the tiles. If @name * vips_dzsave() creates a directory called @name to hold the tiles. If @name
* ends `.zip`, vips_dzsave() will create a zip file called @name to hold the * ends `.zip`, vips_dzsave() will create a zip file called @name to hold the
* tiles. You can use @container to force zip file output. * tiles. You can use @container to force zip file output.
*
* Use @basename to set the name of the directory tree we are creating. The
* default value is set from @name.
* *
* You can set @suffix to something like `".jpg[Q=85]"` to control the tile * You can set @suffix to something like `".jpg[Q=85]"` to control the tile
* write options. * write options.
@ -2292,3 +2435,66 @@ vips_dzsave( VipsImage *in, const char *name, ... )
return( result ); return( result );
} }
/**
* vips_dzsave_buffer:
* @in: image to save
* @buf: return output buffer here
* @len: return output length here
* @...: %NULL-terminated list of optional named arguments
*
* Optional arguments:
*
* * @basename: %gchar, base part of name
* * @layout: #VipsForeignDzLayout directory layout convention
* * @suffix: suffix for tile 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
* * @properties: %gboolean write a properties file
* * @compression: %gint zip deflate compression level
*
* As vips_dzsave(), but save to a memory buffer.
*
* Output is always in a zip container. Use @basename to set the name of the
* directory that the zip will create when unzipped.
*
* The address of the buffer is returned in @buf, the length of the buffer in
* @len. You are responsible for freeing the buffer with g_free() when you
* are done with it.
*
* See also: vips_dzsave(), vips_image_write_to_file().
*
* Returns: 0 on success, -1 on error.
*/
int
vips_dzsave_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( "dzsave_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 );
}

View File

@ -1649,7 +1649,8 @@ vips_foreign_operation_init( void )
extern GType vips_foreign_load_magick_buffer_get_type( void ); extern GType vips_foreign_load_magick_buffer_get_type( void );
extern GType vips_foreign_load_magick7_file_get_type( void ); extern GType vips_foreign_load_magick7_file_get_type( void );
extern GType vips_foreign_load_magick7_buffer_get_type( void ); extern GType vips_foreign_load_magick7_buffer_get_type( void );
extern GType vips_foreign_save_dz_get_type( 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_load_webp_file_get_type( void ); extern GType vips_foreign_load_webp_file_get_type( void );
extern GType vips_foreign_load_webp_buffer_get_type( void ); extern GType vips_foreign_load_webp_buffer_get_type( void );
extern GType vips_foreign_save_webp_file_get_type( void ); extern GType vips_foreign_save_webp_file_get_type( void );
@ -1709,7 +1710,8 @@ vips_foreign_operation_init( void )
#endif /*HAVE_GIFLIB*/ #endif /*HAVE_GIFLIB*/
#ifdef HAVE_GSF #ifdef HAVE_GSF
vips_foreign_save_dz_get_type(); vips_foreign_save_dz_file_get_type();
vips_foreign_save_dz_buffer_get_type();
#endif /*HAVE_GSF*/ #endif /*HAVE_GSF*/
#ifdef HAVE_PNG #ifdef HAVE_PNG

View File

@ -309,10 +309,10 @@ vips_tracked_malloc( size_t size )
vips_error( "vips_tracked", vips_error( "vips_tracked",
_( "out of memory --- size == %dMB" ), _( "out of memory --- size == %dMB" ),
(int) (size / (1024.0*1024.0)) ); (int) (size / (1024.0 * 1024.0)) );
vips_warn( "vips_tracked", vips_warn( "vips_tracked",
_( "out of memory --- size == %dMB" ), _( "out of memory --- size == %dMB" ),
(int) (size / (1024.0*1024.0)) ); (int) (size / (1024.0 * 1024.0)) );
return( NULL ); return( NULL );
} }

View File

@ -703,6 +703,17 @@ class TestForeign(unittest.TestCase):
shutil.rmtree("test_files") shutil.rmtree("test_files")
os.unlink("test.dzi") os.unlink("test.dzi")
# test save to memory buffer
self.colour.dzsave("test-10.zip")
with open("test-10.zip", 'rb') as f:
buf1 = f.read()
os.unlink("test-10.zip")
buf2 = self.colour.dzsave_buffer(basename = "test-10")
self.assertEqual(len(buf1), len(buf2))
# we can't test the bytes are exactly equal, the timestamps will be
# different
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()