diff --git a/ChangeLog b/ChangeLog index e472dbfc..3ca1601a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ +3/10/12 started 7.31.0 +- dzsave can write zoomify and google maps layout as well + 13/9/12 started 7.30.3 - linecache sized itself too large - fix a compile failure if libtiff was not found (thanks Martin) diff --git a/libvips/foreign/dzsave.c b/libvips/foreign/dzsave.c index 2500aeb7..9b0a7c81 100644 --- a/libvips/foreign/dzsave.c +++ b/libvips/foreign/dzsave.c @@ -20,6 +20,8 @@ * 1/10/12 * - did not write low pyramid layers for images with an odd number of * scan lines (thanks Martin) + * 3/10/12 + * - add zoomify and google maps output */ /* @@ -64,6 +66,15 @@ #include #include +#include + +/* Round N down to P boundary. + */ +#define ROUND_DOWN(N,P) ((N) - ((N) % P)) + +/* Round N up to P boundary. + */ +#define ROUND_UP(N,P) (ROUND_DOWN( (N) + (P) - 1, (P) )) typedef struct _VipsForeignSaveDz VipsForeignSaveDz; typedef struct _Layer Layer; @@ -80,6 +91,12 @@ struct _Layer { int width; int height; + /* Number of tiles across and down in this layer. Zoomify needs this + * to calculate the directory to put each tile in. + */ + int tiles_across; + int tiles_down; + VipsImage *image; /* The image we build */ /* The top of this strip of tiles, excluding the overlap. Go up from @@ -111,8 +128,13 @@ struct _VipsForeignSaveDz { char *suffix; int overlap; int tile_size; + VipsForeignDzLayout layout; Layer *layer; /* x2 shrink pyr layer */ + + /* Count zoomify tiles we write. + */ + int tile_count; }; typedef VipsForeignSaveClass VipsForeignSaveDzClass; @@ -151,11 +173,15 @@ pyramid_build( VipsForeignSaveDz *dz, Layer *above, int width, int height ) Layer *layer = VIPS_NEW( dz, Layer ); VipsRect strip; + int limit; layer->dz = dz; layer->width = width; layer->height = height; + layer->tiles_across = ROUND_UP( width, dz->tile_size ) / dz->tile_size; + layer->tiles_down = ROUND_UP( height, dz->tile_size ) / dz->tile_size; + layer->image = NULL; layer->strip = NULL; layer->copy = NULL; @@ -207,8 +233,16 @@ pyramid_build( VipsForeignSaveDz *dz, Layer *above, int width, int height ) return( NULL ); } - if( width > 1 || - height > 1 ) { + /* DeepZoom stops at 1x1 pixels, others when the image fits within a + * tile. + */ + if( dz->layout == VIPS_FOREIGN_DZ_LAYOUT_DZ ) + limit = 1; + else + limit = dz->tile_size; + + if( width > limit || + height > limit ) { /* Round up, so eg. a 5 pixel wide image becomes 3 a layer * down. */ @@ -236,18 +270,20 @@ pyramid_build( VipsForeignSaveDz *dz, Layer *above, int width, int height ) static int pyramid_mkdir( VipsForeignSaveDz *dz ) { - Layer *layer; + char buf[PATH_MAX]; - if( vips_existsf( "%s_files", dz->basename ) ) { + if( dz->layout == VIPS_FOREIGN_DZ_LAYOUT_DZ ) + vips_snprintf( buf, PATH_MAX, "%s_files", dz->basename ); + else + vips_snprintf( buf, PATH_MAX, "%s", dz->basename ); + + if( vips_existsf( "%s", buf ) ) { vips_error( "dzsave", - _( "Directory \"%s_files\" exists" ), dz->basename ); + _( "Directory \"%s\" exists" ), buf ); return( -1 ); } - if( vips_mkdirf( "%s_files", dz->basename ) ) + if( vips_mkdirf( "%s", buf ) ) return( -1 ); - for( layer = dz->layer; layer; layer = layer->below ) - if( vips_mkdirf( "%s_files/%d", dz->basename, layer->n ) ) - return( -1 ); return( 0 ); } @@ -280,6 +316,49 @@ write_dzi( VipsForeignSaveDz *dz ) return( 0 ); } +static int +write_properties( VipsForeignSaveDz *dz ) +{ + FILE *fp; + char buf[PATH_MAX]; + + vips_snprintf( buf, PATH_MAX, "%s/ImageProperties.xml", dz->basename ); + if( !(fp = vips__file_open_write( buf, TRUE )) ) + return( -1 ); + + fprintf( fp, "\n", + dz->layer->width, + dz->layer->height, + dz->tile_count, + dz->tile_size ); + + fclose( fp ); + + return( 0 ); +} + +static int +write_blank( VipsForeignSaveDz *dz ) +{ + char buf[PATH_MAX]; + VipsImage *black; + + vips_snprintf( buf, PATH_MAX, "%s/blank.png", dz->basename ); + if( vips_black( &black, dz->tile_size, dz->tile_size, + "bands", 3, + NULL ) ) + return( -1 ); + if( vips_image_write_to_file( black, buf ) ) { + g_object_unref( black ); + return( -1 ); + } + g_object_unref( black ); + + return( 0 ); +} + /* Generate area @target in @to using pixels in @from. VIPS_CODING_LABQ only. */ static void @@ -492,6 +571,104 @@ strip_allocate( VipsThreadState *state, void *a, gboolean *stop ) return( 0 ); } +/* Create a tile name in the current layout. + */ +static int +tile_name( Layer *layer, char *buf, int x, int y ) +{ + VipsForeignSaveDz *dz = layer->dz; + + char dirname[PATH_MAX]; + char dirname2[PATH_MAX]; + Layer *p; + int n; + + /* We have to lock or there's a race between exists() and mkdir(). + */ + g_mutex_lock( vips__global_lock ); + + switch( dz->layout ) { + case VIPS_FOREIGN_DZ_LAYOUT_DZ: + vips_snprintf( dirname, PATH_MAX, "%s_files/%d", + dz->basename, layer->n ); + vips_snprintf( buf, PATH_MAX, "%s/%d_%d%s", + dirname, x, y, dz->suffix ); + + if( !vips_existsf( "%s", dirname ) && + vips_mkdirf( "%s", dirname ) ) { + g_mutex_unlock( vips__global_lock ); + return( -1 ); + } + + break; + + case VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY: + /* We need to work out the tile number so we can calculate the + * directory to put this tile in. + * + * Tiles are numbered from 0 for the most-zoomed-out tile. + */ + n = 0; + + /* Count all tiles in layers below this one. + */ + for( p = layer->below; p; p = p->below ) + n += p->tiles_across * p->tiles_down; + + /* And count tiles so far in this layer. + */ + n += y * layer->tiles_across + x; + + vips_snprintf( dirname, PATH_MAX, "%s/TileGroup%d", + dz->basename, n / 256 ); + vips_snprintf( buf, PATH_MAX, "%s/%d-%d-%d%s", + dirname, layer->n, x, y, dz->suffix ); + + /* Used at the end in ImageProperties.xml + */ + dz->tile_count += 1; + + if( !vips_existsf( "%s", dirname ) && + vips_mkdirf( "%s", dirname ) ) { + g_mutex_unlock( vips__global_lock ); + return( -1 ); + } + + break; + + case VIPS_FOREIGN_DZ_LAYOUT_GOOGLE: + vips_snprintf( dirname, PATH_MAX, "%s/%d", + dz->basename, layer->n ); + vips_snprintf( dirname2, PATH_MAX, "%s/%d", + dirname, y ); + vips_snprintf( buf, PATH_MAX, "%s/%d%s", + dirname2, x, dz->suffix ); + + if( !vips_existsf( "%s", dirname ) && + vips_mkdirf( "%s", dirname ) ) { + g_mutex_unlock( vips__global_lock ); + return( -1 ); + } + + if( !vips_existsf( "%s", dirname2 ) && + vips_mkdirf( "%s", dirname2 ) ) { + g_mutex_unlock( vips__global_lock ); + return( -1 ); + } + + break; + + default: + g_assert( 0 ); + g_mutex_unlock( vips__global_lock ); + return( -1 ); + } + + g_mutex_unlock( vips__global_lock ); + + return( 0 ); +} + static int strip_work( VipsThreadState *state, void *a ) { @@ -499,29 +676,41 @@ strip_work( VipsThreadState *state, void *a ) Layer *layer = strip->layer; VipsForeignSaveDz *dz = layer->dz; - VipsImage *extr; - char str[1000]; - VipsBuf buf = VIPS_BUF_STATIC( str ); + VipsImage *x; + char buf[PATH_MAX]; + + if( tile_name( layer, buf, + state->x / dz->tile_size, state->y / dz->tile_size ) ) + return( -1 ); /* Extract relative to the strip top-left corner. */ - if( vips_extract_area( state->im, &extr, + if( vips_extract_area( state->im, &x, state->pos.left, 0, state->pos.width, state->pos.height, NULL ) ) return( -1 ); - vips_buf_appendf( &buf, "%s_files/%d/%d_%d%s", - dz->basename, layer->n, - state->x / dz->tile_size, - state->y / dz->tile_size, - dz->suffix ); + /* Google tiles need to be padded up to tilesize. + */ + if( dz->layout == VIPS_FOREIGN_DZ_LAYOUT_GOOGLE ) { + VipsImage *z; - if( vips_image_write_to_file( extr, vips_buf_all( &buf ) ) ) { - g_object_unref( extr ); - return( -1 ); + if( vips_embed( x, &z, 0, 0, dz->tile_size, dz->tile_size, + "extend", VIPS_EXTEND_WHITE, + NULL ) ) { + g_object_unref( x ); + return( -1 ); + } + g_object_unref( x ); + + x = z; } - g_object_unref( extr ); + if( vips_image_write_to_file( x, buf ) ) { + g_object_unref( x ); + return( -1 ); + } + g_object_unref( x ); return( 0 ); } @@ -802,6 +991,16 @@ vips_foreign_save_dz_build( VipsObject *object ) VipsForeignSave *save = (VipsForeignSave *) object; VipsForeignSaveDz *dz = (VipsForeignSaveDz *) object; + /* Google and zoomify default to zero overlap, ".jpg". + */ + if( dz->layout == VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY || + dz->layout == VIPS_FOREIGN_DZ_LAYOUT_GOOGLE ) { + if( !vips_object_get_argument_assigned( object, "overlap" ) ) + dz->overlap = 0; + if( !vips_object_get_argument_assigned( object, "suffix" ) ) + VIPS_SETSTR( dz->suffix, ".jpg" ); + } + if( VIPS_OBJECT_CLASS( vips_foreign_save_dz_parent_class )-> build( object ) ) return( -1 ); @@ -819,13 +1018,33 @@ vips_foreign_save_dz_build( VipsObject *object ) if( !(dz->layer = pyramid_build( dz, NULL, save->ready->Xsize, save->ready->Ysize )) ) return( -1 ); - if( pyramid_mkdir( dz ) || - write_dzi( dz ) ) + if( pyramid_mkdir( dz ) ) return( -1 ); if( vips_sink_disc( save->ready, pyramid_strip, dz ) ) return( -1 ); + switch( dz->layout ) { + case VIPS_FOREIGN_DZ_LAYOUT_DZ: + if( write_dzi( dz ) ) + return( -1 ); + break; + + case VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY: + if( write_properties( dz ) ) + return( -1 ); + break; + + case VIPS_FOREIGN_DZ_LAYOUT_GOOGLE: + if( write_blank( dz ) ) + return( -1 ); + break; + + default: + g_assert( 0 ); + return( -1 ); + } + return( 0 ); } @@ -878,6 +1097,14 @@ vips_foreign_save_dz_class_init( VipsForeignSaveDzClass *class ) G_STRUCT_OFFSET( VipsForeignSaveDz, basename ), NULL ); + VIPS_ARG_ENUM( class, "layout", 8, + _( "Layout" ), + _( "Directory layout" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveDz, layout ), + VIPS_TYPE_FOREIGN_DZ_LAYOUT, + VIPS_FOREIGN_DZ_LAYOUT_DZ ); + VIPS_ARG_STRING( class, "suffix", 9, _( "suffix" ), _( "Filename suffix for tiles" ), @@ -929,8 +1156,10 @@ static void vips_foreign_save_dz_init( VipsForeignSaveDz *dz ) { VIPS_SETSTR( dz->suffix, ".jpeg" ); + dz->layout = VIPS_FOREIGN_DZ_LAYOUT_DZ; dz->overlap = 1; dz->tile_size = 256; + dz->tile_count = 0; } /** @@ -941,24 +1170,27 @@ vips_foreign_save_dz_init( VipsForeignSaveDz *dz ) * * Optional arguments: * - * @suffix: suffix for tile tiles (default ".jpg") - * @overlap; set tile overlap (default 1) - * @tile_size; set tile size (default 256) + * @layout; directory layout convention + * @suffix: suffix for tile tiles + * @overlap; set tile overlap + * @tile_size; set tile size * - * Save an image to a deep zoom - style directory tree. A directory called + * Save an image as a set of tiles at various resolutions. By default dzsave + * uses DeepZoom layout -- use @layout to pick other conventions. + * + * In DeepZoom layout a directory called * "@basename_files" is created to hold the tiles, and an XML file called - * "@basename.dzi" is written with the image metadata, + * "@basename.dzi" is written with the image metadata, * - * The image is shrunk in a series of x2 reductions until it fits within a - * single pixel. Each layer is written out to a separate subdirectory of - * @dirname_files, with directory "0" holding the smallest, single pixel image. - * - * Each tile is written as a separate file named as "@x_@y@suffix", where @x - * and @y are the tile coordinates, with (0, 0) as the top-left tile. + * In Zoomify and Google layout, a directory called @basename is created to + * hold the tile structure. * * You can set @suffix to something like ".jpg[Q=85]" to set the tile write * options. * + * You can set the size and overlap of tiles with @tile_size and @overlap. + * They default to the correct settings for the selected @layout. + * * See also: vips_tiffsave(). * * Returns: 0 on success, -1 on error. diff --git a/libvips/include/vips/enumtypes.h b/libvips/include/vips/enumtypes.h index 44bc558d..1e57c6ac 100644 --- a/libvips/include/vips/enumtypes.h +++ b/libvips/include/vips/enumtypes.h @@ -17,6 +17,8 @@ GType vips_foreign_tiff_predictor_get_type (void) G_GNUC_CONST; #define VIPS_TYPE_FOREIGN_TIFF_PREDICTOR (vips_foreign_tiff_predictor_get_type()) GType vips_foreign_tiff_resunit_get_type (void) G_GNUC_CONST; #define VIPS_TYPE_FOREIGN_TIFF_RESUNIT (vips_foreign_tiff_resunit_get_type()) +GType vips_foreign_dz_layout_get_type (void) G_GNUC_CONST; +#define VIPS_TYPE_FOREIGN_DZ_LAYOUT (vips_foreign_dz_layout_get_type()) /* enumerations from "../../../libvips/include/vips/arithmetic.h" */ GType vips_operation_math_get_type (void) G_GNUC_CONST; #define VIPS_TYPE_OPERATION_MATH (vips_operation_math_get_type()) diff --git a/libvips/include/vips/foreign.h b/libvips/include/vips/foreign.h index 533456cc..322eb635 100644 --- a/libvips/include/vips/foreign.h +++ b/libvips/include/vips/foreign.h @@ -424,6 +424,21 @@ int vips_radload( const char *filename, VipsImage **out, ... ) int vips_radsave( VipsImage *in, const char *filename, ... ) __attribute__((sentinel)); +/** + * VipsForeignDzLayout: + * @VIPS_FOREIGN_DZ_LAYOUT_DZ: use DeepZoom directory layout + * @VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY: use Zoomify directory layout + * @VIPS_FOREIGN_DZ_LAYOUT_GOOGLE: use Google maps directory layout + * + * Use inches or centimeters as the resolution unit for a tiff file. + */ +typedef enum { + VIPS_FOREIGN_DZ_LAYOUT_DZ, + VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY, + VIPS_FOREIGN_DZ_LAYOUT_GOOGLE, + VIPS_FOREIGN_DZ_LAYOUT_LAST +} VipsForeignDzLayout; + int vips_dzsave( VipsImage *in, const char *basename, ... ) __attribute__((sentinel)); diff --git a/libvips/iofuncs/enumtypes.c b/libvips/iofuncs/enumtypes.c index aa2634d7..ba7b357a 100644 --- a/libvips/iofuncs/enumtypes.c +++ b/libvips/iofuncs/enumtypes.c @@ -105,6 +105,25 @@ vips_foreign_tiff_resunit_get_type( void ) return( etype ); } +GType +vips_foreign_dz_layout_get_type( void ) +{ + static GType etype = 0; + + if( etype == 0 ) { + static const GEnumValue values[] = { + {VIPS_FOREIGN_DZ_LAYOUT_DZ, "VIPS_FOREIGN_DZ_LAYOUT_DZ", "dz"}, + {VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY, "VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY", "zoomify"}, + {VIPS_FOREIGN_DZ_LAYOUT_GOOGLE, "VIPS_FOREIGN_DZ_LAYOUT_GOOGLE", "google"}, + {VIPS_FOREIGN_DZ_LAYOUT_LAST, "VIPS_FOREIGN_DZ_LAYOUT_LAST", "last"}, + {0, NULL, NULL} + }; + + etype = g_enum_register_static( "VipsForeignDzLayout", values ); + } + + return( etype ); +} /* enumerations from "../../libvips/include/vips/conversion.h" */ GType vips_extend_get_type( void )