dzsave can write zoomify and google maps

use "layout" to pick the directory layout, deepzoom / zoomify / google
all allowed
This commit is contained in:
John Cupitt 2012-10-03 14:26:59 +01:00
parent aec36aa5a7
commit dee1aac7b1
5 changed files with 306 additions and 35 deletions

View File

@ -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)

View File

@ -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 <string.h>
#include <vips/vips.h>
#include <vips/internal.h>
/* 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, "<IMAGE_PROPERTIES "
"WIDTH=\"%d\" HEIGHT=\"%d\" NUMTILES=\"%d\" "
"NUMIMAGES=\"1\" VERSION=\"1.8\" TILESIZE=\"%d\" />\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.

View File

@ -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())

View File

@ -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));

View File

@ -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 )