From cff84f4606b43f4e10c0f6a71d9f094273495b70 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 2 Jun 2020 12:53:36 +0100 Subject: [PATCH] add subifd pyr support to thumbnail thumbnail can spot subifd pyramids and load lower levels if necessary --- ChangeLog | 4 +- libvips/include/vips/header.h | 1 + libvips/iofuncs/header.c | 25 ++++++- libvips/resample/thumbnail.c | 119 +++++++++++++++++++++++++++++----- 4 files changed, 130 insertions(+), 19 deletions(-) diff --git a/ChangeLog b/ChangeLog index 6b3907e2..7bd10158 100644 --- a/ChangeLog +++ b/ChangeLog @@ -15,13 +15,15 @@ - add subsample_mode, deprecate no_subsample in jpegsave [Elad-Laufer] - add vips_isdirf() - add PAGENUMBER support to tiff write [jclavoie-jive] -- add all to smartcrop +- add "all" mode to smartcrop - flood fill could stop half-way for some very complex shapes - better handling of unaligned reads in multipage tiffs [petoor] - mark old --delete option to vipsthumbnail as deprecated [UweOhse] - png save with a bad ICC profile just gives a warning - add "premultipled" option to vips_affine(), clarified vips_resize() behaviour with alpha channels +- improve bioformats support with read and write of tiff subifd pyramids +- thumbnail exploits subifd pyramids 24/4/20 started 8.9.3 - better iiif tile naming [IllyaMoskvin] diff --git a/libvips/include/vips/header.h b/libvips/include/vips/header.h index 1716f386..d4d09037 100644 --- a/libvips/include/vips/header.h +++ b/libvips/include/vips/header.h @@ -178,6 +178,7 @@ double vips_image_get_scale( const VipsImage *image ); double vips_image_get_offset( const VipsImage *image ); int vips_image_get_page_height( VipsImage *image ); int vips_image_get_n_pages( VipsImage *image ); +int vips_image_get_n_subifds( VipsImage *image ); const void *vips_image_get_data( VipsImage *image ); void vips_image_init_fields( VipsImage *image, diff --git a/libvips/iofuncs/header.c b/libvips/iofuncs/header.c index dba1c767..230dfeb7 100644 --- a/libvips/iofuncs/header.c +++ b/libvips/iofuncs/header.c @@ -818,7 +818,7 @@ vips_image_get_page_height( VipsImage *image ) * vips_image_get_n_pages: (method) * @image: image to get from * - * Fetch and sanity-check VIPS_META_N_PAGES. Default to 1 if not present or + * Fetch and sanity-check #VIPS_META_N_PAGES. Default to 1 if not present or * crazy. * * This is the number of pages in the image file, not the number of pages that @@ -840,6 +840,29 @@ vips_image_get_n_pages( VipsImage *image ) return( 1 ); } +/** + * vips_image_get_n_subifds: (method) + * @image: image to get from + * + * Fetch and sanity-check #VIPS_META_N_SUBIFDS. Default to 0 if not present or + * crazy. + * + * Returns: the number of subifds in the image file + */ +int +vips_image_get_n_subifds( VipsImage *image ) +{ + int n_subifds; + + if( vips_image_get_typeof( image, VIPS_META_N_SUBIFDS ) && + !vips_image_get_int( image, VIPS_META_N_SUBIFDS, &n_subifds ) && + n_subifds > 1 && + n_subifds < 1000 ) + return( n_subifds ); + + return( 0 ); +} + /** * vips_image_get_data: (method) * @image: image to get data for diff --git a/libvips/resample/thumbnail.c b/libvips/resample/thumbnail.c index 5a9c5296..8498ae5c 100644 --- a/libvips/resample/thumbnail.c +++ b/libvips/resample/thumbnail.c @@ -28,6 +28,8 @@ * - smarter heif thumbnail selection * 12/10/19 * - add thumbnail_source + * 2/6/20 + * - add subifd pyr support */ /* @@ -117,6 +119,7 @@ typedef struct _VipsThumbnail { VipsAngle angle; /* From vips_autorot_get_angle() */ int n_pages; /* Pages in file */ int n_loaded_pages; /* Pages we've loaded from file */ + int n_subifds; /* Number of subifds */ /* For openslide, we need to read out the size of each level too. * @@ -131,6 +134,10 @@ typedef struct _VipsThumbnail { int heif_thumbnail_width; int heif_thumbnail_height; + /* For TIFF sources, open subifds rather than pages to get pyr layers. + */ + gboolean subifd_pyramid; + } VipsThumbnail; typedef struct _VipsThumbnailClass { @@ -199,6 +206,7 @@ vips_thumbnail_read_header( VipsThumbnail *thumbnail, VipsImage *image ) thumbnail->angle = vips_autorot_get_angle( image ); thumbnail->page_height = vips_image_get_page_height( image ); thumbnail->n_pages = vips_image_get_n_pages( image ); + thumbnail->n_subifds = vips_image_get_n_subifds( image ); /* VIPS_META_N_PAGES is the number of pages in the document, * not the number we've read out into this image. We calculate @@ -235,15 +243,20 @@ vips_thumbnail_read_header( VipsThumbnail *thumbnail, VipsImage *image ) } } -/* This may not be a pyr tiff, so no error if we can't find the layers. - * We just look for two or more pages following roughly /2 shrinks. +/* Detect a TIFF pyramid made of pages following a roughly /2 shrink. + * + * This may not be a pyr tiff, so no error if we can't find the layers. */ -static void -vips_thumbnail_get_tiff_pyramid( VipsThumbnail *thumbnail ) +static gboolean +vips_thumbnail_get_tiff_pyramid_page( VipsThumbnail *thumbnail ) { VipsThumbnailClass *class = VIPS_THUMBNAIL_GET_CLASS( thumbnail ); int i; +#ifdef DEBUG + printf( "vips_thumbnail_get_tiff_pyramid_page:\n" ); +#endif /*DEBUG*/ + for( i = 0; i < thumbnail->n_pages; i++ ) { VipsImage *page; int level_width; @@ -252,7 +265,7 @@ vips_thumbnail_get_tiff_pyramid( VipsThumbnail *thumbnail ) int expected_level_height; if( !(page = class->open( thumbnail, i )) ) - return; + return( FALSE ); level_width = page->Xsize; level_height = page->Ysize; VIPS_UNREF( page ); @@ -264,10 +277,10 @@ vips_thumbnail_get_tiff_pyramid( VipsThumbnail *thumbnail ) */ if( abs( level_width - expected_level_width ) > 5 || level_width < 2 ) - return; + return( FALSE ); if( abs( level_height - expected_level_height ) > 5 || level_height < 2 ) - return; + return( FALSE ); thumbnail->level_width[i] = level_width; thumbnail->level_height[i] = level_height; @@ -276,10 +289,74 @@ vips_thumbnail_get_tiff_pyramid( VipsThumbnail *thumbnail ) /* Now set level_count. This signals that we've found a pyramid. */ #ifdef DEBUG - printf( "vips_thumbnail_get_tiff_pyramid: %d layer pyramid detected\n", - thumbnail->n_pages ); + printf( "vips_thumbnail_get_tiff_pyramid_page: " + "%d layer pyramid detected\n", + thumbnail->n_pages ); #endif /*DEBUG*/ thumbnail->level_count = thumbnail->n_pages; + + return( thumbnail->n_pages > 1 ); +} + +/* Detect a TIFF pyramid made of subifds following a roughly /2 shrink. + * + * This may not be a pyr tiff, so no error if we can't find the layers. + */ +static gboolean +vips_thumbnail_get_tiff_pyramid_subifd( VipsThumbnail *thumbnail ) +{ + VipsThumbnailClass *class = VIPS_THUMBNAIL_GET_CLASS( thumbnail ); + int i; + +#ifdef DEBUG + printf( "vips_thumbnail_get_tiff_pyramid_subifd:\n" ); +#endif /*DEBUG*/ + + /* Tell open() that we want to open subifds rather than pages. + */ + thumbnail->subifd_pyramid = TRUE; + + for( i = 0; i < thumbnail->n_subifds; i++ ) { + VipsImage *page; + int level_width; + int level_height; + int expected_level_width; + int expected_level_height; + + if( !(page = class->open( thumbnail, i )) ) + return( FALSE ); + level_width = page->Xsize; + level_height = page->Ysize; + VIPS_UNREF( page ); + + /* The main image is size 1, subifd 0 is half that. + */ + expected_level_width = thumbnail->input_width / (2 << i); + expected_level_height = thumbnail->input_height / (2 << i); + + /* This won't be exact due to rounding etc. + */ + if( abs( level_width - expected_level_width ) > 5 || + level_width < 2 ) + return( FALSE ); + if( abs( level_height - expected_level_height ) > 5 || + level_height < 2 ) + return( FALSE ); + + thumbnail->level_width[i] = level_width; + thumbnail->level_height[i] = level_height; + } + + /* Now set level_count. This signals that we've found a pyramid. + */ +#ifdef DEBUG + printf( "vips_thumbnail_get_tiff_pyramid_subifd: " + "%d layer pyramid detected\n", + thumbnail->n_pages ); +#endif /*DEBUG*/ + thumbnail->level_count = thumbnail->n_pages; + + return( TRUE ); } static int @@ -459,11 +536,13 @@ vips_thumbnail_open( VipsThumbnail *thumbnail ) g_info( "input size is %d x %d", thumbnail->input_width, thumbnail->input_height ); - /* For tiff, we need a separate ->open() for each page to - * get all the pyramid levels. + /* For tiff, scan the image and try to spot page-based and ifd-based + * pyramids. */ - if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ) - vips_thumbnail_get_tiff_pyramid( thumbnail ); + if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ) { + if( !vips_thumbnail_get_tiff_pyramid_page( thumbnail ) ) + vips_thumbnail_get_tiff_pyramid_subifd( thumbnail ); + } /* For heif, we need to fetch the thumbnail size, in case we can use * that as the source. @@ -967,10 +1046,16 @@ vips_thumbnail_file_open( VipsThumbnail *thumbnail, double factor ) NULL ) ); } else if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ) { - return( vips_image_new_from_file( file->filename, - "access", VIPS_ACCESS_SEQUENTIAL, - "page", (int) factor, - NULL ) ); + if( thumbnail->subifd_pyramid ) + return( vips_image_new_from_file( file->filename, + "access", VIPS_ACCESS_SEQUENTIAL, + "subifd", (int) factor, + NULL ) ); + else + return( vips_image_new_from_file( file->filename, + "access", VIPS_ACCESS_SEQUENTIAL, + "page", (int) factor, + NULL ) ); } else if( vips_isprefix( "VipsForeignLoadHeif", thumbnail->loader ) ) { return( vips_image_new_from_file( file->filename,