implement shrink-on-load for openslide thumbs

makes vipsthumbnail much quicker on openslide images, obviously

see https://github.com/libvips/libvips/issues/1149
This commit is contained in:
John Cupitt 2018-10-31 14:07:13 +00:00
parent aaf26765f2
commit 22ba9106b5
2 changed files with 150 additions and 57 deletions

View File

@ -3,6 +3,7 @@
- add low/high to smartcrop [jcupitt] - add low/high to smartcrop [jcupitt]
- add XMP support to png read/write [jcupitt] - add XMP support to png read/write [jcupitt]
- deprecate thumbnail auto_rotate, add no_rotate [jcupitt] - deprecate thumbnail auto_rotate, add no_rotate [jcupitt]
- implement thumbnail shrink-on-load for openslide images [jcupitt]
23/9/18 started 8.7.1 23/9/18 started 8.7.1
- update function list in docs [janko-m] - update function list in docs [janko-m]

View File

@ -13,6 +13,7 @@
* - add intent option, thanks kleisauke * - add intent option, thanks kleisauke
* 31/10/18 * 31/10/18
* - deprecate auto_rotate, add no_rotate * - deprecate auto_rotate, add no_rotate
* - implement shrink-on-load for openslide images
*/ */
/* /*
@ -72,6 +73,10 @@
(G_TYPE_INSTANCE_GET_CLASS( (obj), \ (G_TYPE_INSTANCE_GET_CLASS( (obj), \
VIPS_TYPE_THUMBNAIL, VipsThumbnailClass )) VIPS_TYPE_THUMBNAIL, VipsThumbnailClass ))
/* Should be plenty.
*/
#define MAX_LEVELS (256)
typedef struct _VipsThumbnail { typedef struct _VipsThumbnail {
VipsOperation parent_instance; VipsOperation parent_instance;
@ -88,18 +93,20 @@ typedef struct _VipsThumbnail {
char *import_profile; char *import_profile;
VipsIntent intent; VipsIntent intent;
/* Set by subclasses to the input image.
*/
VipsImage *in;
/* Bits of info we read from the input image when we get the header of /* Bits of info we read from the input image when we get the header of
* the original. * the original.
*/ */
const char *loader; /* Eg. "jpegload_buffer" */ const char *loader; /* Eg. "VipsForeignLoadJpeg*" */
int input_width; int input_width;
int input_height; int input_height;
VipsAngle angle; /* From vips_autorot_get_angle() */ VipsAngle angle; /* From vips_autorot_get_angle() */
/* For openslide, we need to read out the size of each level too.
*/
int level_count;
int level_width[MAX_LEVELS];
int level_height[MAX_LEVELS];
} VipsThumbnail; } VipsThumbnail;
typedef struct _VipsThumbnailClass { typedef struct _VipsThumbnailClass {
@ -109,11 +116,13 @@ typedef struct _VipsThumbnailClass {
*/ */
int (*get_info)( VipsThumbnail *thumbnail ); int (*get_info)( VipsThumbnail *thumbnail );
/* Open, giving either a scale or a shrink. @shrink is an integer shrink /* Open with some kind of shrink or scale factor. Exactly what we pass
* factor suitable for vips_jpegload() or equivalent, @scale is a * and to what param depends on the loader. It'll be an integer shrink
* double scale factor, suitable for vips_svgload() or similar. * factor for vips_jpegload(), a double scale factor for vips_svgload().
*
* See VipsThumbnail::loader
*/ */
VipsImage *(*open)( VipsThumbnail *thumbnail, int shrink, double scale ); VipsImage *(*open)( VipsThumbnail *thumbnail, double factor );
} VipsThumbnailClass; } VipsThumbnailClass;
@ -143,6 +152,53 @@ vips_thumbnail_finalize( GObject *gobject )
G_OBJECT_CLASS( vips_thumbnail_parent_class )->finalize( gobject ); G_OBJECT_CLASS( vips_thumbnail_parent_class )->finalize( gobject );
} }
/* Fetch an int openslide field from metadata. These are all represented as
* strings. Return the defaulyt value if there's any problem.
*/
static int
get_int( VipsImage *image, const char *field, int default_value )
{
const char *str;
if( vips_image_get_typeof( image, field ) &&
!vips_image_get_string( image, field, &str ) )
return( atoi( str ) );
return( default_value );
}
static void
vips_thumbnail_read_header( VipsThumbnail *thumbnail, VipsImage *image )
{
thumbnail->input_width = image->Xsize;
thumbnail->input_height = image->Ysize;
thumbnail->angle = vips_autorot_get_angle( image );
/* For openslide, read out the level structure too.
*/
if( vips_isprefix( "VipsForeignLoadOpenslide", thumbnail->loader ) ) {
int level_count;
int level;
level_count = get_int( image, "openslide.level-count", 1 );
level_count = VIPS_CLIP( 1, level_count, MAX_LEVELS );
thumbnail->level_count = level_count;
for( level = 0; level < level_count; level++ ) {
char name[256];
vips_snprintf( name, 256,
"openslide.level[%d].width", level );
thumbnail->level_width[level] =
get_int( image, name, 0 );
vips_snprintf( name, 256,
"openslide.level[%d].height", level );
thumbnail->level_height[level] =
get_int( image, name, 0 );
}
}
}
/* Calculate the shrink factor, taking into account auto-rotate, the fit mode, /* Calculate the shrink factor, taking into account auto-rotate, the fit mode,
* and so on. * and so on.
* *
@ -225,7 +281,8 @@ vips_thumbnail_calculate_common_shrink( VipsThumbnail *thumbnail,
/* Find the best jpeg preload shrink. /* Find the best jpeg preload shrink.
*/ */
static int static int
vips_thumbnail_find_jpegshrink( VipsThumbnail *thumbnail, int width, int height ) vips_thumbnail_find_jpegshrink( VipsThumbnail *thumbnail,
int width, int height )
{ {
double shrink = vips_thumbnail_calculate_common_shrink( thumbnail, double shrink = vips_thumbnail_calculate_common_shrink( thumbnail,
width, height ); width, height );
@ -253,6 +310,26 @@ vips_thumbnail_find_jpegshrink( VipsThumbnail *thumbnail, int width, int height
return( 1 ); return( 1 );
} }
/* Find the best openslide level.
*/
static int
vips_thumbnail_find_openslidelevel( VipsThumbnail *thumbnail,
int width, int height )
{
int level;
g_assert( thumbnail->level_count > 0 );
g_assert( thumbnail->level_count <= MAX_LEVELS );
for( level = thumbnail->level_count - 1; level >= 0; level-- )
if( vips_thumbnail_calculate_common_shrink( thumbnail,
thumbnail->level_width[level],
thumbnail->level_height[level] ) >= 1.0 )
return( level );
return( 0 );
}
/* Open the image, returning the best version for thumbnailing. /* Open the image, returning the best version for thumbnailing.
* *
* For example, libjpeg supports fast shrink-on-read, so if we have a JPEG, * For example, libjpeg supports fast shrink-on-read, so if we have a JPEG,
@ -264,8 +341,7 @@ vips_thumbnail_open( VipsThumbnail *thumbnail )
VipsThumbnailClass *class = VIPS_THUMBNAIL_GET_CLASS( thumbnail ); VipsThumbnailClass *class = VIPS_THUMBNAIL_GET_CLASS( thumbnail );
VipsImage *im; VipsImage *im;
double shrink; double factor;
double scale;
if( class->get_info( thumbnail ) ) if( class->get_info( thumbnail ) )
return( NULL ); return( NULL );
@ -273,32 +349,40 @@ vips_thumbnail_open( VipsThumbnail *thumbnail )
g_info( "input size is %d x %d", g_info( "input size is %d x %d",
thumbnail->input_width, thumbnail->input_height ); thumbnail->input_width, thumbnail->input_height );
shrink = 1.0; factor = 1.0;
scale = 1.0;
if( vips_isprefix( "VipsForeignLoadJpeg", thumbnail->loader ) ) { if( vips_isprefix( "VipsForeignLoadJpeg", thumbnail->loader ) ) {
shrink = vips_thumbnail_find_jpegshrink( thumbnail, factor = vips_thumbnail_find_jpegshrink( thumbnail,
thumbnail->input_width, thumbnail->input_height ); thumbnail->input_width, thumbnail->input_height );
g_info( "loading jpeg with factor %g pre-shrink", shrink ); g_info( "loading jpeg with factor %g pre-shrink", factor );
}
else if( vips_isprefix( "VipsForeignLoadOpenslide",
thumbnail->loader ) ) {
factor = vips_thumbnail_find_openslidelevel( thumbnail,
thumbnail->input_width, thumbnail->input_height );
g_info( "loading openslide level %g", factor );
} }
else if( vips_isprefix( "VipsForeignLoadPdf", thumbnail->loader ) || else if( vips_isprefix( "VipsForeignLoadPdf", thumbnail->loader ) ||
vips_isprefix( "VipsForeignLoadSvg", thumbnail->loader ) ) { vips_isprefix( "VipsForeignLoadSvg", thumbnail->loader ) ) {
scale = 1.0 / vips_thumbnail_calculate_common_shrink( thumbnail, factor = 1.0 /
thumbnail->input_width, thumbnail->input_height ); vips_thumbnail_calculate_common_shrink( thumbnail,
thumbnail->input_width,
thumbnail->input_height );
g_info( "loading PDF/SVG with factor %g pre-scale", scale ); g_info( "loading PDF/SVG with factor %g pre-scale", factor );
} }
else if( vips_isprefix( "VipsForeignLoadWebp", thumbnail->loader ) ) { else if( vips_isprefix( "VipsForeignLoadWebp", thumbnail->loader ) ) {
shrink = VIPS_MAX( 1.0, factor = VIPS_MAX( 1.0,
vips_thumbnail_calculate_common_shrink( thumbnail, vips_thumbnail_calculate_common_shrink( thumbnail,
thumbnail->input_width, thumbnail->input_width,
thumbnail->input_height ) ); thumbnail->input_height ) );
g_info( "loading webp with factor %g pre-shrink", shrink ); g_info( "loading webp with factor %g pre-shrink", factor );
} }
if( !(im = class->open( thumbnail, shrink, scale )) ) if( !(im = class->open( thumbnail, factor )) )
return( NULL ); return( NULL );
return( im ); return( im );
@ -668,43 +752,47 @@ vips_thumbnail_file_get_info( VipsThumbnail *thumbnail )
!(image = vips_image_new_from_file( file->filename, NULL )) ) !(image = vips_image_new_from_file( file->filename, NULL )) )
return( -1 ); return( -1 );
thumbnail->input_width = image->Xsize; vips_thumbnail_read_header( thumbnail, image );
thumbnail->input_height = image->Ysize;
thumbnail->angle = vips_autorot_get_angle( image );
g_object_unref( image ); g_object_unref( image );
return( 0 ); return( 0 );
} }
/* Open an image, pre-shrinking as appropriate. Some formats use shrink, some /* Open an image, pre-shrinking as appropriate.
* scale, never both.
*/ */
static VipsImage * static VipsImage *
vips_thumbnail_file_open( VipsThumbnail *thumbnail, int shrink, double scale ) vips_thumbnail_file_open( VipsThumbnail *thumbnail, double factor )
{ {
VipsThumbnailFile *file = (VipsThumbnailFile *) thumbnail; VipsThumbnailFile *file = (VipsThumbnailFile *) thumbnail;
/* If both shrink and scale have been set, something is wrong. It if( vips_isprefix( "VipsForeignLoadJpeg", thumbnail->loader ) ) {
* should be one or the other (or neither).
*/
g_assert( shrink == 1 || scale == 1.0 );
if( shrink != 1 )
return( vips_image_new_from_file( file->filename, return( vips_image_new_from_file( file->filename,
"access", VIPS_ACCESS_SEQUENTIAL, "access", VIPS_ACCESS_SEQUENTIAL,
"shrink", shrink, "shrink", (int) factor,
NULL ) ); NULL ) );
else if( scale != 1.0 ) }
else if( vips_isprefix( "VipsForeignLoadOpenslide",
thumbnail->loader ) ) {
return( vips_image_new_from_file( file->filename, return( vips_image_new_from_file( file->filename,
"access", VIPS_ACCESS_SEQUENTIAL, "access", VIPS_ACCESS_SEQUENTIAL,
"scale", scale, "level", (int) factor,
NULL ) ); NULL ) );
else }
else if( vips_isprefix( "VipsForeignLoadPdf", thumbnail->loader ) ||
vips_isprefix( "VipsForeignLoadSvg", thumbnail->loader ) ||
vips_isprefix( "VipsForeignLoadWebp", thumbnail->loader ) ) {
return( vips_image_new_from_file( file->filename,
"access", VIPS_ACCESS_SEQUENTIAL,
"scale", factor,
NULL ) );
}
else {
return( vips_image_new_from_file( file->filename, return( vips_image_new_from_file( file->filename,
"access", VIPS_ACCESS_SEQUENTIAL, "access", VIPS_ACCESS_SEQUENTIAL,
NULL ) ); NULL ) );
} }
}
static void static void
vips_thumbnail_file_class_init( VipsThumbnailClass *class ) vips_thumbnail_file_class_init( VipsThumbnailClass *class )
@ -842,44 +930,51 @@ vips_thumbnail_buffer_get_info( VipsThumbnail *thumbnail )
buffer->buf->data, buffer->buf->length, "", NULL )) ) buffer->buf->data, buffer->buf->length, "", NULL )) )
return( -1 ); return( -1 );
thumbnail->input_width = image->Xsize; vips_thumbnail_read_header( thumbnail, image );
thumbnail->input_height = image->Ysize;
thumbnail->angle = vips_autorot_get_angle( image );
g_object_unref( image ); g_object_unref( image );
return( 0 ); return( 0 );
} }
/* Open an image, pre-shrinking as appropriate. Some formats use shrink, some /* Open an image, scaling as appropriate.
* scale, never both.
*/ */
static VipsImage * static VipsImage *
vips_thumbnail_buffer_open( VipsThumbnail *thumbnail, vips_thumbnail_buffer_open( VipsThumbnail *thumbnail, double factor )
int shrink, double scale )
{ {
VipsThumbnailBuffer *buffer = (VipsThumbnailBuffer *) thumbnail; VipsThumbnailBuffer *buffer = (VipsThumbnailBuffer *) thumbnail;
/* We can't use UNBUFERRED safely on very-many-core systems. if( vips_isprefix( "VipsForeignLoadJpeg", thumbnail->loader ) ) {
*/
if( shrink != 1 )
return( vips_image_new_from_buffer( return( vips_image_new_from_buffer(
buffer->buf->data, buffer->buf->length, "", buffer->buf->data, buffer->buf->length, "",
"access", VIPS_ACCESS_SEQUENTIAL, "access", VIPS_ACCESS_SEQUENTIAL,
"shrink", shrink, "shrink", (int) factor,
NULL ) ); NULL ) );
else if( scale != 1.0 ) }
else if( vips_isprefix( "VipsForeignLoadOpenslide",
thumbnail->loader ) ) {
return( vips_image_new_from_buffer( return( vips_image_new_from_buffer(
buffer->buf->data, buffer->buf->length, "", buffer->buf->data, buffer->buf->length, "",
"access", VIPS_ACCESS_SEQUENTIAL, "access", VIPS_ACCESS_SEQUENTIAL,
"scale", scale, "level", (int) factor,
NULL ) ); NULL ) );
else }
else if( vips_isprefix( "VipsForeignLoadPdf", thumbnail->loader ) ||
vips_isprefix( "VipsForeignLoadSvg", thumbnail->loader ) ||
vips_isprefix( "VipsForeignLoadWebp", thumbnail->loader ) ) {
return( vips_image_new_from_buffer(
buffer->buf->data, buffer->buf->length, "",
"access", VIPS_ACCESS_SEQUENTIAL,
"scale", factor,
NULL ) );
}
else {
return( vips_image_new_from_buffer( return( vips_image_new_from_buffer(
buffer->buf->data, buffer->buf->length, "", buffer->buf->data, buffer->buf->length, "",
"access", VIPS_ACCESS_SEQUENTIAL, "access", VIPS_ACCESS_SEQUENTIAL,
NULL ) ); NULL ) );
} }
}
static void static void
vips_thumbnail_buffer_class_init( VipsThumbnailClass *class ) vips_thumbnail_buffer_class_init( VipsThumbnailClass *class )
@ -978,9 +1073,7 @@ vips_thumbnail_image_get_info( VipsThumbnail *thumbnail )
*/ */
thumbnail->loader = "image source"; thumbnail->loader = "image source";
thumbnail->input_width = image->in->Xsize; vips_thumbnail_read_header( thumbnail, image->in );
thumbnail->input_height = image->in->Ysize;
thumbnail->angle = vips_autorot_get_angle( image->in );
return( 0 ); return( 0 );
} }
@ -988,8 +1081,7 @@ vips_thumbnail_image_get_info( VipsThumbnail *thumbnail )
/* Open an image. We can't pre-shrink with an image source, sadly. /* Open an image. We can't pre-shrink with an image source, sadly.
*/ */
static VipsImage * static VipsImage *
vips_thumbnail_image_open( VipsThumbnail *thumbnail, vips_thumbnail_image_open( VipsThumbnail *thumbnail, double factor )
int shrink, double scale )
{ {
VipsThumbnailImage *image = (VipsThumbnailImage *) thumbnail; VipsThumbnailImage *image = (VipsThumbnailImage *) thumbnail;