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:
parent
aaf26765f2
commit
22ba9106b5
@ -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]
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user