diff --git a/ChangeLog b/ChangeLog index 5cd95dc8..52e1e299 100644 --- a/ChangeLog +++ b/ChangeLog @@ -37,6 +37,7 @@ - dzsave to szi will write all associated images - remove old c++ and python interfaces - vipsthumbnail can thumbnail animated and multipage images +- deprecate webpload @shrink, use @scale instead 31/3/19 started 8.7.5 - better buffer sizing in tiff reader [omira-sch] diff --git a/libvips/foreign/magicksave.c b/libvips/foreign/magicksave.c index e468a669..fc32f8dc 100644 --- a/libvips/foreign/magicksave.c +++ b/libvips/foreign/magicksave.c @@ -143,9 +143,18 @@ vips_foreign_save_magick_next_image( VipsForeignSaveMagick *magick ) !vips_image_get_int( im, "gif-delay", &number ) ) image->delay = (size_t) number; + /* ImageMagick uses iterations like this (at least in gif save): + * 0 - set 0 loops (infinite) + * 1 - don't write the netscape extension block + * 2 - loop once + * 3 - loop twice etc. + * + * We have the simple gif meaning, so we must add one unless it's + * zero. + */ if( vips_image_get_typeof( im, "gif-loop" ) && !vips_image_get_int( im, "gif-loop", &number ) ) - image->iterations = (size_t) number; + image->iterations = (size_t) (number ? number + 1 : 0); if( vips_image_get_typeof( im, "gif-comment" ) && !vips_image_get_string( im, "gif-comment", &str ) ) diff --git a/libvips/foreign/pdfload.c b/libvips/foreign/pdfload.c index a2655994..49f211e7 100644 --- a/libvips/foreign/pdfload.c +++ b/libvips/foreign/pdfload.c @@ -297,8 +297,12 @@ vips_foreign_load_pdf_header( VipsForeignLoad *load ) poppler_page_get_size( pdf->page, &width, &height ); pdf->pages[i].left = 0; pdf->pages[i].top = top; - pdf->pages[i].width = width * pdf->scale; - pdf->pages[i].height = height * pdf->scale; + /* We do round to nearest, in the same way that vips_resize() + * does round to nearest. Without this, things like + * shrink-on-load will break. + */ + pdf->pages[i].width = VIPS_RINT( width * pdf->scale ); + pdf->pages[i].height = VIPS_RINT( height * pdf->scale ); if( pdf->pages[i].width > pdf->image.width ) pdf->image.width = pdf->pages[i].width; diff --git a/libvips/foreign/pdfload_pdfium.c b/libvips/foreign/pdfload_pdfium.c index 94f7c266..5f62d43c 100644 --- a/libvips/foreign/pdfload_pdfium.c +++ b/libvips/foreign/pdfload_pdfium.c @@ -341,10 +341,14 @@ vips_foreign_load_pdf_header( VipsForeignLoad *load ) return( -1 ); pdf->pages[i].left = 0; pdf->pages[i].top = top; - pdf->pages[i].width = - FPDF_GetPageWidth( pdf->page ) * pdf->scale; - pdf->pages[i].height = - FPDF_GetPageHeight( pdf->page ) * pdf->scale; + /* We do round to nearest, in the same way that vips_resize() + * does round to nearest. Without this, things like + * shrink-on-load will break. + */ + pdf->pages[i].width = VIPS_RINT( + FPDF_GetPageWidth( pdf->page ) * pdf->scale ); + pdf->pages[i].height = VIPS_RINT( + FPDF_GetPageHeight( pdf->page ) * pdf->scale ); if( pdf->pages[i].width > pdf->image.width ) pdf->image.width = pdf->pages[i].width; diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index cee9102a..0f2c304f 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -226,14 +226,14 @@ int vips__iswebp_buffer( const void *buf, size_t len ); int vips__iswebp( const char *filename ); int vips__webp_read_file_header( const char *name, VipsImage *out, - int page, int n, int shrink ); + int page, int n, double scale ); int vips__webp_read_file( const char *name, VipsImage *out, - int page, int n, int shrink ); + int page, int n, double scale ); int vips__webp_read_buffer_header( const void *buf, size_t len, VipsImage *out, - int page, int n, int shrink ); + int page, int n, double scale ); int vips__webp_read_buffer( const void *buf, size_t len, VipsImage *out, - int page, int n, int shrink ); + int page, int n, double scale ); int vips__webp_write_file( VipsImage *out, const char *filename, int Q, gboolean lossless, VipsForeignWebpPreset preset, diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index a79658fb..dfd30fa7 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -2345,25 +2345,7 @@ rtiff_new_buffer( const void *buf, size_t len, VipsImage *out, return( rtiff ); } -/* - - FIXME ... Unused for now, perhaps if we add another format flag. - -static int -istiffpyramid( const char *name ) -{ - TIFF *tif; - - vips__tiff_init(); - - if( (tif = get_directory( name, 2 )) ) { - // We can see page 2 ... assume it is. - TIFFClose( tif ); - return( 1 ); - } - - return( 0 ); -} +/* For istiffpyramid(), see vips_thumbnail_get_tiff_pyramid(). */ int @@ -2379,7 +2361,8 @@ vips__tiff_read( const char *filename, VipsImage *out, vips__tiff_init(); - if( !(rtiff = rtiff_new_filename( filename, out, page, n, autorotate )) ) + if( !(rtiff = rtiff_new_filename( filename, out, + page, n, autorotate )) ) return( -1 ); if( rtiff->header.tiled ) { @@ -2394,7 +2377,7 @@ vips__tiff_read( const char *filename, VipsImage *out, return( 0 ); } -/* On a header-only read, we can just swap width/height if orientaion is 6 or +/* On a header-only read, we can just swap width/height if orientation is 6 or * 8. */ static void diff --git a/libvips/foreign/webp2vips.c b/libvips/foreign/webp2vips.c index 5bb3e20c..cbe999ca 100644 --- a/libvips/foreign/webp2vips.c +++ b/libvips/foreign/webp2vips.c @@ -17,6 +17,9 @@ * - could memleak on some read errors * 24/4/19 * - fix bg handling in animations + * 30/4/19 + * - deprecate shrink, use scale instead, and make it a double ... this + * lets us do faster and more accurate thumbnailing */ /* @@ -90,9 +93,9 @@ typedef struct { */ int n; - /* Shrink-on-load factor. Use this to set scaled_width. + /* Scale-on-load factor. Use this to set scaled_width. */ - int shrink; + double scale; /* Size of final output image. */ @@ -150,6 +153,39 @@ typedef struct { VipsRect dispose_rect; } Read; +const char * +vips__error_webp( VP8StatusCode code ) +{ + switch( code ) { + case VP8_STATUS_OK: + return( "VP8_STATUS_OK" ); + + case VP8_STATUS_OUT_OF_MEMORY: + return( "VP8_STATUS_OUT_OF_MEMORY" ); + + case VP8_STATUS_INVALID_PARAM: + return( "VP8_STATUS_INVALID_PARAM" ); + + case VP8_STATUS_BITSTREAM_ERROR: + return( "VP8_STATUS_BITSTREAM_ERROR" ); + + case VP8_STATUS_UNSUPPORTED_FEATURE: + return( "VP8_STATUS_UNSUPPORTED_FEATURE" ); + + case VP8_STATUS_SUSPENDED: + return( "VP8_STATUS_SUSPENDED" ); + + case VP8_STATUS_USER_ABORT: + return( "VP8_STATUS_USER_ABORT" ); + + case VP8_STATUS_NOT_ENOUGH_DATA: + return( "VP8_STATUS_NOT_ENOUGH_DATA" ); + + default: + return( "" ); + } +} + static void vips_image_paint_area( VipsImage *image, const VipsRect *r, const VipsPel *ink ) { @@ -230,32 +266,34 @@ blend_pixel( guint32 A, guint32 B ) return( setRGBA( rR, gR, bR, aR ) ); } +/* Blend sub into frame at left, top. + */ static void -vips_image_paint_image( VipsImage *image, - VipsImage *ink, int x, int y, gboolean blend ) +vips_image_paint_image( VipsImage *frame, + VipsImage *sub, int left, int top, gboolean blend ) { - VipsRect valid = { 0, 0, image->Xsize, image->Ysize }; - VipsRect sub = { x, y, ink->Xsize, ink->Ysize }; - int ps = VIPS_IMAGE_SIZEOF_PEL( image ); + VipsRect frame_rect = { 0, 0, frame->Xsize, frame->Ysize }; + VipsRect sub_rect = { left, top, sub->Xsize, sub->Ysize }; + int ps = VIPS_IMAGE_SIZEOF_PEL( frame ); VipsRect ovl; - g_assert( VIPS_IMAGE_SIZEOF_PEL( ink ) == ps ); + g_assert( VIPS_IMAGE_SIZEOF_PEL( sub ) == ps ); /* Disable blend if we are not RGBA. */ - if( image->Bands != 4 ) + if( frame->Bands != 4 ) blend = FALSE; - vips_rect_intersectrect( &valid, &sub, &ovl ); + vips_rect_intersectrect( &frame_rect, &sub_rect, &ovl ); if( !vips_rect_isempty( &ovl ) ) { VipsPel *p, *q; - int i; + int x, y; - p = VIPS_IMAGE_ADDR( ink, ovl.left - x, ovl.top - y ); - q = VIPS_IMAGE_ADDR( image, ovl.left, ovl.top ); + p = VIPS_IMAGE_ADDR( sub, ovl.left - left, ovl.top - top ); + q = VIPS_IMAGE_ADDR( frame, ovl.left, ovl.top ); - for( i = 0; i < ovl.height; i++ ) { + for( y = 0; y < ovl.height; y++ ) { if( blend ) { guint32 *A = (guint32 *) p; guint32 *B = (guint32 *) q; @@ -267,8 +305,8 @@ vips_image_paint_image( VipsImage *image, memcpy( (char *) q, (char *) p, ovl.width * ps ); - p += VIPS_IMAGE_SIZEOF_LINE( ink ); - q += VIPS_IMAGE_SIZEOF_LINE( image ); + p += VIPS_IMAGE_SIZEOF_LINE( sub ); + q += VIPS_IMAGE_SIZEOF_LINE( frame ); } } } @@ -325,7 +363,7 @@ read_free( Read *read ) static Read * read_new( const char *filename, const void *data, size_t length, - int page, int n, int shrink ) + int page, int n, double scale ) { Read *read; @@ -337,7 +375,7 @@ read_new( const char *filename, const void *data, size_t length, read->length = length; read->page = page; read->n = n; - read->shrink = shrink; + read->scale = scale; read->delay = 100; read->fd = 0; read->demux = NULL; @@ -394,14 +432,17 @@ read_header( Read *read, VipsImage *out ) canvas_width = WebPDemuxGetI( read->demux, WEBP_FF_CANVAS_WIDTH ); canvas_height = WebPDemuxGetI( read->demux, WEBP_FF_CANVAS_HEIGHT ); - read->frame_width = canvas_width / read->shrink; - read->frame_height = canvas_height / read->shrink; + /* We round-to-nearest cf. pdfload etc. + */ + read->frame_width = VIPS_RINT( canvas_width * read->scale ); + read->frame_height = VIPS_RINT( canvas_height * read->scale ); - if( read->shrink > 1 ) { - read->config.options.use_scaling = 1; - read->config.options.scaled_width = read->frame_width; - read->config.options.scaled_height = read->frame_height; - } +#ifdef DEBUG + printf( "webp2vips: canvas_width = %d\n", canvas_width ); + printf( "webp2vips: canvas_height = %d\n", canvas_height ); + printf( "webp2vips: frame_width = %d\n", read->frame_width ); + printf( "webp2vips: frame_height = %d\n", read->frame_height ); +#endif /*DEBUG*/ flags = WebPDemuxGetI( read->demux, WEBP_FF_FORMAT_FLAGS ); @@ -521,11 +562,11 @@ read_header( Read *read, VipsImage *out ) int vips__webp_read_file_header( const char *filename, VipsImage *out, - int page, int n, int shrink ) + int page, int n, double scale ) { Read *read; - if( !(read = read_new( filename, NULL, 0, page, n, shrink )) ) { + if( !(read = read_new( filename, NULL, 0, page, n, scale )) ) { vips_error( "webp2vips", _( "unable to open \"%s\"" ), filename ); return( -1 ); @@ -541,6 +582,9 @@ vips__webp_read_file_header( const char *filename, VipsImage *out, return( 0 ); } +/* Read a single frame -- a width * height block of pixels. This will get + * blended into the accumulator at some offset. + */ static VipsImage * read_frame( Read *read, int width, int height, const guint8 *data, size_t length ) @@ -553,7 +597,7 @@ read_frame( Read *read, frame = vips_image_new_memory(); vips_image_init_fields( frame, - width, height, + width, height, read->alpha ? 4 : 3, VIPS_FORMAT_UCHAR, VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, @@ -568,6 +612,11 @@ read_frame( Read *read, read->config.output.u.RGBA.rgba = VIPS_IMAGE_ADDR( frame, 0, 0 ); read->config.output.u.RGBA.stride = VIPS_IMAGE_SIZEOF_LINE( frame ); read->config.output.u.RGBA.size = VIPS_IMAGE_SIZEOF_IMAGE( frame ); + if( read->scale != 1.0 ) { + read->config.options.use_scaling = 1; + read->config.options.scaled_width = width; + read->config.options.scaled_height = height; + } if( WebPDecode( data, length, &read->config ) != VP8_STATUS_OK ) { g_object_unref( frame ); @@ -582,16 +631,26 @@ static int read_next_frame( Read *read ) { VipsImage *frame; + VipsRect area; #ifdef DEBUG printf( "read_next_frame:\n" ); #endif /*DEBUG*/ + /* Area of this frame, in output image coordinates. We must rint(), + * since we need the same rules as the overall image scale, or we'll + * sometimes have missing pixels on edges. + */ + area.left = VIPS_RINT( read->iter.x_offset * read->scale ); + area.top = VIPS_RINT( read->iter.y_offset * read->scale ); + area.width = VIPS_RINT( read->iter.width * read->scale ); + area.height = VIPS_RINT( read->iter.height * read->scale ); + /* Dispose from the previous frame. */ if( read->dispose_method == WEBP_MUX_DISPOSE_BACKGROUND ) { - /* We must clear the pixels occupied by this webp frame (not - * the whole of the read frame) to 0 (transparent). + /* We must clear the pixels occupied by the previous webp + * frame (not the whole of the read frame) to 0 (transparent). * * We do not clear to WEBP_FF_BACKGROUND_COLOR. That's only * used to composite down to RGB. Perhaps we @@ -606,17 +665,14 @@ read_next_frame( Read *read ) /* Note this frame's dispose for next time. */ read->dispose_method = read->iter.dispose_method; - read->dispose_rect.left = read->iter.x_offset; - read->dispose_rect.top = read->iter.y_offset; - read->dispose_rect.width = read->iter.width; - read->dispose_rect.height = read->iter.height; + read->dispose_rect = area; #ifdef DEBUG printf( "webp2vips: frame_num = %d\n", read->iter.frame_num ); - printf( " x_offset = %d\n", read->iter.x_offset ); - printf( " y_offset = %d\n", read->iter.y_offset ); - printf( " width = %d\n", read->iter.width ); - printf( " height = %d\n", read->iter.height ); + printf( " left = %d\n", area.left ); + printf( " top = %d\n", area.top ); + printf( " width = %d\n", area.width ); + printf( " height = %d\n", area.height ); printf( " duration = %d\n", read->iter.duration ); printf( " dispose = " ); if( read->iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND ) @@ -637,14 +693,14 @@ read_next_frame( Read *read ) "not all frames have equal duration" ); if( !(frame = read_frame( read, - read->iter.width, read->iter.height, + area.width, area.height, read->iter.fragment.bytes, read->iter.fragment.size )) ) return( -1 ); /* Now blend or copy the new pixels into our accumulator. */ vips_image_paint_image( read->frame, frame, - read->iter.x_offset, read->iter.y_offset, + area.left, area.top, read->iter.blend_method == WEBP_MUX_BLEND ); g_object_unref( frame ); @@ -715,11 +771,11 @@ read_image( Read *read, VipsImage *out ) int vips__webp_read_file( const char *filename, VipsImage *out, - int page, int n, int shrink ) + int page, int n, double scale ) { Read *read; - if( !(read = read_new( filename, NULL, 0, page, n, shrink )) ) { + if( !(read = read_new( filename, NULL, 0, page, n, scale )) ) { vips_error( "webp2vips", _( "unable to open \"%s\"" ), filename ); return( -1 ); @@ -737,11 +793,11 @@ vips__webp_read_file( const char *filename, VipsImage *out, int vips__webp_read_buffer_header( const void *buf, size_t len, VipsImage *out, - int page, int n, int shrink ) + int page, int n, double scale ) { Read *read; - if( !(read = read_new( NULL, buf, len, page, n, shrink )) ) { + if( !(read = read_new( NULL, buf, len, page, n, scale )) ) { vips_error( "webp2vips", "%s", _( "unable to open buffer" ) ); return( -1 ); @@ -759,11 +815,11 @@ vips__webp_read_buffer_header( const void *buf, size_t len, VipsImage *out, int vips__webp_read_buffer( const void *buf, size_t len, VipsImage *out, - int page, int n, int shrink ) + int page, int n, double scale ) { Read *read; - if( !(read = read_new( NULL, buf, len, page, n, shrink )) ) { + if( !(read = read_new( NULL, buf, len, page, n, scale )) ) { vips_error( "webp2vips", "%s", _( "unable to open buffer" ) ); return( -1 ); diff --git a/libvips/foreign/webpload.c b/libvips/foreign/webpload.c index 367dbd72..545fc793 100644 --- a/libvips/foreign/webpload.c +++ b/libvips/foreign/webpload.c @@ -6,6 +6,8 @@ * - add @shrink * 1/11/18 * - add @page, @n + * 30/4/19 + * - deprecate @shrink, use @scale instead */ /* @@ -64,9 +66,13 @@ typedef struct _VipsForeignLoadWebp { */ int n; - /* Shrink by this much during load. + /* Scale by this much during load. */ - int shrink; + double scale; + + /* Old and deprecated scaling path. + */ + int shrink; } VipsForeignLoadWebp; typedef VipsForeignLoadClass VipsForeignLoadWebpClass; @@ -120,10 +126,20 @@ vips_foreign_load_webp_class_init( VipsForeignLoadWebpClass *class ) G_STRUCT_OFFSET( VipsForeignLoadWebp, n ), -1, 100000, 1 ); - VIPS_ARG_INT( class, "shrink", 22, + VIPS_ARG_DOUBLE( class, "scale", 22, + _( "Scale" ), + _( "Scale factor on load" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadWebp, scale ), + 0.0, 1024.0, 1.0 ); + + /* Old and deprecated scaling API. A float param lets do + * shrink-on-load for thumbnail faster and more accurately. + */ + VIPS_ARG_INT( class, "shrink", 23, _( "Shrink" ), _( "Shrink factor on load" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, + VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, G_STRUCT_OFFSET( VipsForeignLoadWebp, shrink ), 1, 1024, 1 ); @@ -134,6 +150,7 @@ vips_foreign_load_webp_init( VipsForeignLoadWebp *webp ) { webp->n = 1; webp->shrink = 1; + webp->scale = 1.0; } typedef struct _VipsForeignLoadWebpFile { @@ -168,8 +185,15 @@ vips_foreign_load_webp_file_header( VipsForeignLoad *load ) VipsForeignLoadWebp *webp = (VipsForeignLoadWebp *) load; VipsForeignLoadWebpFile *file = (VipsForeignLoadWebpFile *) load; + /* BC for the old API. + */ + if( !vips_object_argument_isset( VIPS_OBJECT( load ), "scale" ) && + vips_object_argument_isset( VIPS_OBJECT( load ), "shrink" ) && + webp->shrink != 0 ) + webp->scale = 1.0 / webp->shrink; + if( vips__webp_read_file_header( file->filename, load->out, - webp->page, webp->n, webp->shrink ) ) + webp->page, webp->n, webp->scale ) ) return( -1 ); VIPS_SETSTR( load->out->filename, file->filename ); @@ -184,7 +208,7 @@ vips_foreign_load_webp_file_load( VipsForeignLoad *load ) VipsForeignLoadWebpFile *file = (VipsForeignLoadWebpFile *) load; if( vips__webp_read_file( file->filename, load->real, - webp->page, webp->n, webp->shrink ) ) + webp->page, webp->n, webp->scale ) ) return( -1 ); return( 0 ); @@ -249,7 +273,7 @@ vips_foreign_load_webp_buffer_header( VipsForeignLoad *load ) if( vips__webp_read_buffer_header( buffer->buf->data, buffer->buf->length, load->out, - webp->page, webp->n, webp->shrink ) ) + webp->page, webp->n, webp->scale ) ) return( -1 ); return( 0 ); @@ -263,7 +287,7 @@ vips_foreign_load_webp_buffer_load( VipsForeignLoad *load ) if( vips__webp_read_buffer( buffer->buf->data, buffer->buf->length, load->real, - webp->page, webp->n, webp->shrink ) ) + webp->page, webp->n, webp->scale ) ) return( -1 ); return( 0 ); @@ -317,7 +341,7 @@ vips_foreign_load_webp_buffer_init( VipsForeignLoadWebpBuffer *buffer ) * * * @page: %gint, page (frame) to read * * @n: %gint, load this many pages - * * @shrink: %gint, shrink by this much on load + * * @scale: %gdouble, scale by this much on load * * Read a WebP file into a VIPS image. * @@ -328,7 +352,8 @@ vips_foreign_load_webp_buffer_init( VipsForeignLoadWebpBuffer *buffer ) * left. Set to -1 to mean "until the end of the document". Use vips_grid() * to change page layout. * - * Use @shrink to specify a shrink-on-load factor. + * Use @scale to specify a scale-on-load factor. For example, 2.0 to double + * the size on load. * * The loader supports ICC, EXIF and XMP metadata. * @@ -360,7 +385,7 @@ vips_webpload( const char *filename, VipsImage **out, ... ) * * * @page: %gint, page (frame) to read * * @n: %gint, load this many pages - * * @shrink: %gint, shrink by this much on load + * * @scale: %gdouble, scale by this much on load * * Read a WebP-formatted memory block into a VIPS image. Exactly as * vips_webpload(), but read from a memory buffer. diff --git a/libvips/iofuncs/generate.c b/libvips/iofuncs/generate.c index 81ca7c90..65be8d9c 100644 --- a/libvips/iofuncs/generate.c +++ b/libvips/iofuncs/generate.c @@ -635,7 +635,7 @@ write_vips( VipsRegion *region, VipsRect *area, void *a, void *b ) size_t nwritten, count; void *buf; - count = region->bpl * area->height; + count = (size_t) region->bpl * area->height; buf = VIPS_REGION_ADDR( region, 0, area->top ); do { diff --git a/libvips/resample/thumbnail.c b/libvips/resample/thumbnail.c index e5a27e36..e9de9ddd 100644 --- a/libvips/resample/thumbnail.c +++ b/libvips/resample/thumbnail.c @@ -109,17 +109,16 @@ typedef struct _VipsThumbnail { int input_height; int page_height; VipsAngle angle; /* From vips_autorot_get_angle() */ + int n_pages; /* Pages in this image, not original */ /* For openslide, we need to read out the size of each level too. + * + * These are filled out for pyr tiffs as well. */ int level_count; int level_width[MAX_LEVELS]; int level_height[MAX_LEVELS]; - /* Try to get n-pages too, for pyr tiff load. - */ - int n_pages; - /* For HEIF, try to fetch the size of the stored thumbnail. */ int heif_thumbnail_width; @@ -191,14 +190,16 @@ 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 ); + thumbnail->page_height = vips_image_get_page_height( image ); - if( vips_image_get_typeof( image, VIPS_META_N_PAGES ) ) { - int n_pages; - - if( !vips_image_get_int( image, VIPS_META_N_PAGES, &n_pages ) ) - thumbnail->n_pages = - VIPS_CLIP( 1, n_pages, MAX_LEVELS ); - } + /* The "n-pages" metadata item is the number of pages in the document, + * not the number we've read out into this image. We calculate + * ourselves from page_height. + * + * vips_image_get_page_height() verifies that Ysize is a simple + * multiple of page_height. + */ + thumbnail->n_pages = thumbnail->input_height / thumbnail->page_height; /* For openslide, read out the level structure too. */ @@ -226,6 +227,7 @@ 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. */ static void vips_thumbnail_get_tiff_pyramid( VipsThumbnail *thumbnail ) @@ -233,6 +235,11 @@ vips_thumbnail_get_tiff_pyramid( VipsThumbnail *thumbnail ) VipsThumbnailClass *class = VIPS_THUMBNAIL_GET_CLASS( thumbnail ); int i; + /* Only one page? Can't be. + */ + if( thumbnail->n_pages < 2 ) + return; + for( i = 0; i < thumbnail->n_pages; i++ ) { VipsImage *page; int level_width; @@ -246,13 +253,10 @@ vips_thumbnail_get_tiff_pyramid( VipsThumbnail *thumbnail ) level_height = page->Ysize; VIPS_UNREF( page ); - /* Try to sanity-check the size of the pages. Do they look - * like a pyramid? - */ expected_level_width = thumbnail->input_width / (1 << i); expected_level_height = thumbnail->input_height / (1 << i); - /* Won't be exact due to rounding etc. + /* This won't be exact due to rounding etc. */ if( abs( level_width - expected_level_width ) > 5 || level_width < 2 ) @@ -405,7 +409,7 @@ vips_thumbnail_find_jpegshrink( VipsThumbnail *thumbnail, return( 1 ); } -/* Find the best openslide level. +/* Find the best pyramid (openslide or tiff) level. */ static int vips_thumbnail_find_pyrlevel( VipsThumbnail *thumbnail, @@ -444,7 +448,7 @@ vips_thumbnail_open( VipsThumbnail *thumbnail ) g_info( "input size is %d x %d", thumbnail->input_width, thumbnail->input_height ); - /* For tiff, we need to make a separate get_info() for each page to + /* For tiff, we need a separate ->open() for each page to * get all the pyramid levels. */ if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ) @@ -456,6 +460,10 @@ vips_thumbnail_open( VipsThumbnail *thumbnail ) if( vips_isprefix( "VipsForeignLoadHeif", thumbnail->loader ) ) vips_thumbnail_get_heif_thumb_info( thumbnail ); + /* We read the openslide level structure in + * vips_thumbnail_read_header(). + */ + factor = 1.0; if( vips_isprefix( "VipsForeignLoadJpeg", thumbnail->loader ) ) { @@ -465,20 +473,28 @@ vips_thumbnail_open( VipsThumbnail *thumbnail ) g_info( "loading jpeg with factor %g pre-shrink", factor ); } else if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) || - vips_isprefix( "VipsForeignLoadOpenslide", thumbnail->loader ) ) { + vips_isprefix( "VipsForeignLoadOpenslide", + thumbnail->loader ) ) { factor = vips_thumbnail_find_pyrlevel( thumbnail, thumbnail->input_width, thumbnail->input_height ); g_info( "loading pyr level %g", factor ); } - else if( vips_isprefix( "VipsForeignLoadPdf", thumbnail->loader ) || - vips_isprefix( "VipsForeignLoadSvg", thumbnail->loader ) ) { + else if( vips_isprefix( "VipsForeignLoadPdf", thumbnail->loader ) ) { + factor = 1.0 / + vips_thumbnail_calculate_common_shrink( thumbnail, + thumbnail->input_width, + thumbnail->page_height ); + + g_info( "loading PDF with factor %g pre-scale", factor ); + } + else if( vips_isprefix( "VipsForeignLoadSvg", thumbnail->loader ) ) { factor = 1.0 / vips_thumbnail_calculate_common_shrink( thumbnail, thumbnail->input_width, thumbnail->input_height ); - g_info( "loading PDF/SVG with factor %g pre-scale", factor ); + g_info( "loading SVG with factor %g pre-scale", factor ); } else if( vips_isprefix( "VipsForeignLoadHeif", thumbnail->loader ) ) { /* 'factor' is a gboolean which enables thumbnail load instead @@ -493,24 +509,14 @@ vips_thumbnail_open( VipsThumbnail *thumbnail ) factor = 0.0; } - - /* Webp supports shrink-on-load, but unfortunately the filter is just - * too odd. - * - * Perhaps reenable this if webp improves. - * - * vips_thumbnail_file_open() and vips_thumbnail_buffer_open() would - * need additional cases as well. - * else if( vips_isprefix( "VipsForeignLoadWebp", thumbnail->loader ) ) { - factor = VIPS_MAX( 1.0, + factor = 1.0 / vips_thumbnail_calculate_common_shrink( thumbnail, thumbnail->input_width, - thumbnail->input_height ) ); + thumbnail->page_height ); - g_info( "loading webp with factor %g pre-shrink", factor ); + g_info( "loading webp with factor %g pre-scale", factor ); } - */ if( !(im = class->open( thumbnail, factor )) ) return( NULL ); @@ -529,6 +535,8 @@ vips_thumbnail_build( VipsObject *object ) VIPS_INTERPRETATION_scRGB : VIPS_INTERPRETATION_sRGB; VipsImage *in; + int preshrunk_page_height; + int output_page_height; double hshrink; double vshrink; @@ -569,10 +577,9 @@ vips_thumbnail_build( VipsObject *object ) return( -1 ); in = t[0]; - /* So page_height is after pre-shrink, but before the main shrink - * stage. + /* After pre-shrink, but before the main shrink stage. */ - thumbnail->page_height = vips_image_get_page_height( in ); + preshrunk_page_height = vips_image_get_page_height( in ); /* RAD needs special unpacking. */ @@ -653,17 +660,17 @@ vips_thumbnail_build( VipsObject *object ) in = t[3]; } - /* Shrink to page_height, so we work for multi-page images. + /* Shrink to preshrunk_page_height, so we work for multi-page images. */ vips_thumbnail_calculate_shrink( thumbnail, - in->Xsize, thumbnail->page_height, &hshrink, &vshrink ); + in->Xsize, preshrunk_page_height, &hshrink, &vshrink ); /* In toilet-roll mode, we must adjust vshrink so that we exactly hit - * page_height or we'll have pixels straddling pixel boundaries. + * page_height or we'll have pixels straddling page boundaries. */ - if( in->Ysize > thumbnail->page_height ) { + if( in->Ysize > preshrunk_page_height ) { int target_page_height = VIPS_RINT( - thumbnail->page_height / vshrink ); + preshrunk_page_height / vshrink ); int target_image_height = target_page_height * thumbnail->n_pages; @@ -676,8 +683,9 @@ vips_thumbnail_build( VipsObject *object ) return( -1 ); in = t[4]; - thumbnail->page_height = VIPS_RINT( thumbnail->page_height / vshrink ); - vips_image_set_int( in, VIPS_META_PAGE_HEIGHT, thumbnail->page_height ); + output_page_height = VIPS_RINT( preshrunk_page_height / vshrink ); + vips_image_set_int( in, + VIPS_META_PAGE_HEIGHT, output_page_height ); if( have_premultiplied ) { g_info( "unpremultiplying alpha" ); @@ -942,7 +950,8 @@ vips_thumbnail_file_open( VipsThumbnail *thumbnail, double factor ) NULL ) ); } else if( vips_isprefix( "VipsForeignLoadPdf", thumbnail->loader ) || - vips_isprefix( "VipsForeignLoadSvg", 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, @@ -1137,7 +1146,8 @@ vips_thumbnail_buffer_open( VipsThumbnail *thumbnail, double factor ) NULL ) ); } else if( vips_isprefix( "VipsForeignLoadPdf", thumbnail->loader ) || - vips_isprefix( "VipsForeignLoadSvg", thumbnail->loader ) ) { + vips_isprefix( "VipsForeignLoadSvg", thumbnail->loader ) || + vips_isprefix( "VipsForeignLoadWebp", thumbnail->loader ) ) { return( vips_image_new_from_buffer( buffer->buf->data, buffer->buf->length, buffer->option_string, diff --git a/test/test-suite/test_foreign.py b/test/test-suite/test_foreign.py index 83c65b21..1affbab3 100644 --- a/test/test-suite/test_foreign.py +++ b/test/test-suite/test_foreign.py @@ -597,7 +597,7 @@ class TestForeign: def pdf_valid(im): a = im(10, 10) assert_almost_equal_objects(a, [35, 31, 32, 255]) - assert im.width == 1133 + assert im.width == 1134 assert im.height == 680 assert im.bands == 4