From f499cefb0e4b4673bb220638108bb634e3f3a511 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 9 Nov 2019 20:40:39 +0000 Subject: [PATCH] add iiif layout to dzsave --- ChangeLog | 1 + libvips/foreign/dzsave.c | 133 ++++++++++++++++++++++++++++++++- libvips/include/vips/foreign.h | 2 + libvips/iofuncs/enumtypes.c | 1 + 4 files changed, 135 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 5cb415a2..7061949a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -17,6 +17,7 @@ - nifti load/save uses double for all floating point metadata - add vips_error_buffer_copy() - add @no_strip option to dzsave [kalozka1] +- add iiif layout to dzsave 31/8/19 started 8.8.3 - revert sharpen restoring the input colourspace diff --git a/libvips/foreign/dzsave.c b/libvips/foreign/dzsave.c index 5a4c95d7..9f1bf5cb 100644 --- a/libvips/foreign/dzsave.c +++ b/libvips/foreign/dzsave.c @@ -83,6 +83,8 @@ * - add @skip_blanks * 21/10/19 * - add @no_strip + * 9/11/19 + * - add IIIF layout */ /* @@ -937,6 +939,86 @@ write_blank( VipsForeignSaveDz *dz ) return( 0 ); } +/* Write IIIF JSON metadata. + */ +static int +write_json( VipsForeignSaveDz *dz ) +{ + GsfOutput *out; + char buf[VIPS_PATH_MAX]; + int i; + + out = vips_gsf_path( dz->tree, "info.json", NULL ); + + gsf_output_printf( out, + "{\n" + " \"@context\": \"http://iiif.io/api/image/2/context.json\",\n" + " \"@id\": \"https://example.com/iiif/apple\",\n" + " \"profile\": [\n" + " \"http://iiif.io/api/image/2/level0.json\",\n" + " {\n" + " \"formats\": [\n" + " \"jpg\"\n" + " ],\n" + " \"qualities\": [\n" + " \"default\"\n" + " ]\n" + " }\n" + " ],\n" + " \"protocol\": \"http://iiif.io/api/image\",\n" + " \"sizes\": [\n" ); + + for( i = 0; i < dz->layer->n + 5; i++ ) { + gsf_output_printf( out, + " {\n" + " \"width\": %d,\n" + " \"height\": \"full\"\n" + " }", + 1 << (i + 4) ); + if( i != dz->layer->n - 4 ) + gsf_output_printf( out, "," ); + gsf_output_printf( out, "\n" ); + } + + gsf_output_printf( out, + " ],\n" ); + + gsf_output_printf( out, + " \"tiles\": [\n" + " {\n" + " \"scalefactors\": [\n" ); + + for( i = 0; i < dz->layer->n; i++ ) { + gsf_output_printf( out, + " %d", + 1 << i ); + if( i != dz->layer->n - 1 ) + gsf_output_printf( out, "," ); + gsf_output_printf( out, "\n" ); + } + + gsf_output_printf( out, + " ],\n" + " \"width\": %d\n" + " }\n" + " ],\n", dz->tile_size ); + + gsf_output_printf( out, + " \"width\": %d,\n" + " \"height\": %d\n", + dz->layer->image->Xsize, + dz->layer->image->Ysize ); + + gsf_output_printf( out, + "}\n" ); + + (void) gsf_output_close( out ); + g_object_unref( out ); + + return( 0 ); +} + + static int write_vips_meta( VipsForeignSaveDz *dz ) { @@ -1306,6 +1388,40 @@ tile_name( Layer *layer, int x, int y ) break; + case VIPS_FOREIGN_DZ_LAYOUT_IIIF: +{ + /* Tiles are addressed in full resolution coordinates, so + * scale up by layer->sub and dz->tile_size + */ + int left = x * dz->tile_size * layer->sub; + int top = y * dz->tile_size * layer->sub; + int width = VIPS_MIN( dz->tile_size * layer->sub, + layer->width * layer->sub - left ); + int height = VIPS_MIN( dz->tile_size * layer->sub, + layer->height * layer->sub - top ); + + /* IIIF "size" is just real tile width, I think. + * + * TODO .. .is this right? shouldn't it be the smaller of + * width and height? + */ + int size = VIPS_MIN( dz->tile_size, + layer->width - x * dz->tile_size ); + + vips_snprintf( dirname, VIPS_PATH_MAX, "%d,%d,%d,%d", + left, top, width, height ); + vips_snprintf( dirname2, VIPS_PATH_MAX, "%d,", size ); + vips_snprintf( name, VIPS_PATH_MAX, "default%s", + dz->file_suffix ); + + /* "0" is rotation and is always 0. + */ + out = vips_gsf_path( dz->tree, + name, dirname, dirname2, "0", NULL ); +} + + break; + default: g_assert_not_reached(); @@ -1814,10 +1930,11 @@ vips_foreign_save_dz_build( VipsObject *object ) VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( dz ); VipsRect real_pixels; - /* Google and zoomify default to zero overlap, ".jpg". + /* Google, zoomify and iiif default to zero overlap, ".jpg". */ if( dz->layout == VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY || - dz->layout == VIPS_FOREIGN_DZ_LAYOUT_GOOGLE ) { + dz->layout == VIPS_FOREIGN_DZ_LAYOUT_GOOGLE || + dz->layout == VIPS_FOREIGN_DZ_LAYOUT_IIIF ) { if( !vips_object_argument_isset( object, "overlap" ) ) dz->overlap = 0; if( !vips_object_argument_isset( object, "suffix" ) ) @@ -1832,6 +1949,13 @@ vips_foreign_save_dz_build( VipsObject *object ) dz->tile_size = 256; } + /* Some iif writers default to 256, some to 512. We pick 512. + */ + if( dz->layout == VIPS_FOREIGN_DZ_LAYOUT_IIIF ) { + if( !vips_object_argument_isset( object, "tile_size" ) ) + dz->tile_size = 512; + } + /* skip_blanks defaults to 5 in google mode. */ if( dz->layout == VIPS_FOREIGN_DZ_LAYOUT_GOOGLE && @@ -2136,6 +2260,11 @@ vips_foreign_save_dz_build( VipsObject *object ) return( -1 ); break; + case VIPS_FOREIGN_DZ_LAYOUT_IIIF: + if( write_json( dz ) ) + return( -1 ); + break; + default: g_assert_not_reached(); } diff --git a/libvips/include/vips/foreign.h b/libvips/include/vips/foreign.h index 432394fc..32200780 100644 --- a/libvips/include/vips/foreign.h +++ b/libvips/include/vips/foreign.h @@ -588,6 +588,7 @@ int vips_niftisave( VipsImage *in, const char *filename, ... ) * @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 + * @VIPS_FOREIGN_DZ_LAYOUT_IIIF: use IIIF directory layout * * What directory layout and metadata standard to use. */ @@ -595,6 +596,7 @@ typedef enum { VIPS_FOREIGN_DZ_LAYOUT_DZ, VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY, VIPS_FOREIGN_DZ_LAYOUT_GOOGLE, + VIPS_FOREIGN_DZ_LAYOUT_IIIF, VIPS_FOREIGN_DZ_LAYOUT_LAST } VipsForeignDzLayout; diff --git a/libvips/iofuncs/enumtypes.c b/libvips/iofuncs/enumtypes.c index a79b0bb8..c22f1ff7 100644 --- a/libvips/iofuncs/enumtypes.c +++ b/libvips/iofuncs/enumtypes.c @@ -612,6 +612,7 @@ vips_foreign_dz_layout_get_type( void ) {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_IIIF, "VIPS_FOREIGN_DZ_LAYOUT_IIIF", "iiif"}, {VIPS_FOREIGN_DZ_LAYOUT_LAST, "VIPS_FOREIGN_DZ_LAYOUT_LAST", "last"}, {0, NULL, NULL} };