From 156b0433d5ea6f37e96669cb5bf369ef89aa0419 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 15 Oct 2016 19:01:34 +0100 Subject: [PATCH] add dzsave_buffer zip only ... see https://github.com/jcupitt/libvips/issues/415 --- ChangeLog | 1 + libvips/foreign/dzsave.c | 340 ++++++++++++++++++++++++++++++-------- libvips/foreign/foreign.c | 6 +- libvips/iofuncs/memory.c | 4 +- test/test_foreign.py | 11 ++ 5 files changed, 291 insertions(+), 71 deletions(-) diff --git a/ChangeLog b/ChangeLog index b0aac74c..c4879390 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,7 @@ 13/10/16 started 8.5.0 - rewritten buffer system is safer and frees memory earlier - added tiff save to buffer +- added dzsave save to buffer (zip only) 27/9/16 started 8.4.2 - small doc improvements diff --git a/libvips/foreign/dzsave.c b/libvips/foreign/dzsave.c index 9a3b096a..fae5584b 100644 --- a/libvips/foreign/dzsave.c +++ b/libvips/foreign/dzsave.c @@ -67,6 +67,8 @@ * - more overlap changes to help gmaps mode * 8/9/16 Felix Bünemann * - 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 { VipsForeignSave parent_object; - /* Name to write to. - */ - char *name; - char *suffix; int overlap; int tile_size; @@ -614,11 +612,16 @@ struct _VipsForeignSaveDz { */ 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; - /* @name, but just the path at the front. + /* The directory we write the output to, or NULL for memory output. */ char *dirname; @@ -650,7 +653,7 @@ struct _VipsForeignSaveDz { typedef VipsForeignSaveClass VipsForeignSaveDzClass; -G_DEFINE_TYPE( VipsForeignSaveDz, vips_foreign_save_dz, +G_DEFINE_ABSTRACT_TYPE( VipsForeignSaveDz, vips_foreign_save_dz, VIPS_TYPE_FOREIGN_SAVE ); /* Free a pyramid. @@ -672,6 +675,7 @@ vips_foreign_save_dz_dispose( GObject *gobject ) VIPS_FREEF( layer_free, dz->layer ); VIPS_FREEF( vips_gsf_tree_free, dz->tree ); + VIPS_FREEF( g_object_unref, dz->out ); VIPS_FREE( dz->basename ); VIPS_FREE( dz->dirname ); VIPS_FREE( dz->tempdir ); @@ -1819,36 +1823,6 @@ vips_foreign_save_dz_build( VipsObject *object ) save->ready->Xsize, save->ready->Ysize, &real_pixels )) ) 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 ) dz->root_name = g_strdup_printf( "%s_files", dz->basename ); else @@ -1869,6 +1843,7 @@ vips_foreign_save_dz_build( VipsObject *object ) */ 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" ), @@ -1934,31 +1909,30 @@ vips_foreign_save_dz_build( VipsObject *object ) case VIPS_FOREIGN_DZ_CONTAINER_ZIP: { - GsfOutput *out; GsfOutput *zip; GsfOutput *out2; GError *error = NULL; 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", - dz->dirname, dz->basename ); - if( !(out = gsf_output_stdio_new( name, &error )) ) { - vips_g_error( &error ); - return( -1 ); + if( dz->dirname ) { + vips_snprintf( name, VIPS_PATH_MAX, "%s/%s.zip", + dz->dirname, dz->basename ); + if( !(dz->out = gsf_output_stdio_new( name, &error )) ) { + vips_g_error( &error ); + return( -1 ); + } } + else + dz->out = gsf_output_memory_new(); if( !(zip = (GsfOutput *) - gsf_outfile_zip_new( out, &error )) ) { + gsf_outfile_zip_new( dz->out, &error )) ) { vips_g_error( &error ); 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 * this. */ @@ -2084,7 +2058,7 @@ vips_foreign_save_dz_class_init( VipsForeignSaveDzClass *class ) gobject_class->set_property = vips_object_set_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->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->coding[VIPS_CODING_LABQ] = TRUE; - VIPS_ARG_STRING( class, "filename", 1, - _( "Filename" ), - _( "Filename to save to" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignSaveDz, name ), + VIPS_ARG_STRING( class, "basename", 2, + _( "Base name" ), + _( "Base name to save to" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveDz, basename ), NULL ); VIPS_ARG_ENUM( class, "layout", 8, @@ -2178,17 +2152,10 @@ vips_foreign_save_dz_class_init( VipsForeignSaveDzClass *class ) */ VIPS_ARG_STRING( class, "dirname", 1, - _( "Base name" ), - _( "Base name to save to" ), - VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, - G_STRUCT_OFFSET( VipsForeignSaveDz, name ), - 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 ), + _( "Directory name" ), + _( "Directory name to save to" ), + VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, + G_STRUCT_OFFSET( VipsForeignSaveDz, dirname ), NULL ); VIPS_ARG_INT( class, "tile_width", 12, @@ -2221,6 +2188,178 @@ vips_foreign_save_dz_init( VipsForeignSaveDz *dz ) 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*/ /** @@ -2231,6 +2370,7 @@ vips_foreign_save_dz_init( VipsForeignSaveDz *dz ) * * Optional arguments: * + * * @basename: %gchar, base part of name * * @layout: #VipsForeignDzLayout directory layout convention * * @suffix: suffix for tile tiles * * @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 * 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 * write options. @@ -2292,3 +2435,66 @@ vips_dzsave( VipsImage *in, const char *name, ... ) 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 ); +} diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index 3095ed55..3313b9e6 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -1649,7 +1649,8 @@ vips_foreign_operation_init( 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_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_buffer_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*/ #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*/ #ifdef HAVE_PNG diff --git a/libvips/iofuncs/memory.c b/libvips/iofuncs/memory.c index d6c00173..c45dbdc1 100644 --- a/libvips/iofuncs/memory.c +++ b/libvips/iofuncs/memory.c @@ -309,10 +309,10 @@ vips_tracked_malloc( size_t size ) vips_error( "vips_tracked", _( "out of memory --- size == %dMB" ), - (int) (size / (1024.0*1024.0)) ); + (int) (size / (1024.0 * 1024.0)) ); vips_warn( "vips_tracked", _( "out of memory --- size == %dMB" ), - (int) (size / (1024.0*1024.0)) ); + (int) (size / (1024.0 * 1024.0)) ); return( NULL ); } diff --git a/test/test_foreign.py b/test/test_foreign.py index 1ca7e1eb..5698793c 100755 --- a/test/test_foreign.py +++ b/test/test_foreign.py @@ -703,6 +703,17 @@ class TestForeign(unittest.TestCase): shutil.rmtree("test_files") 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__': unittest.main()