Merge branch 'master' into revise-composite

This commit is contained in:
John Cupitt 2019-05-08 16:14:01 +01:00
commit 447e0f3ea2
11 changed files with 229 additions and 137 deletions

@ -37,6 +37,7 @@
- dzsave to szi will write all associated images - dzsave to szi will write all associated images
- remove old c++ and python interfaces - remove old c++ and python interfaces
- vipsthumbnail can thumbnail animated and multipage images - vipsthumbnail can thumbnail animated and multipage images
- deprecate webpload @shrink, use @scale instead
31/3/19 started 8.7.5 31/3/19 started 8.7.5
- better buffer sizing in tiff reader [omira-sch] - better buffer sizing in tiff reader [omira-sch]

@ -143,9 +143,18 @@ vips_foreign_save_magick_next_image( VipsForeignSaveMagick *magick )
!vips_image_get_int( im, "gif-delay", &number ) ) !vips_image_get_int( im, "gif-delay", &number ) )
image->delay = (size_t) 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" ) && if( vips_image_get_typeof( im, "gif-loop" ) &&
!vips_image_get_int( im, "gif-loop", &number ) ) !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" ) && if( vips_image_get_typeof( im, "gif-comment" ) &&
!vips_image_get_string( im, "gif-comment", &str ) ) !vips_image_get_string( im, "gif-comment", &str ) )

@ -297,8 +297,12 @@ vips_foreign_load_pdf_header( VipsForeignLoad *load )
poppler_page_get_size( pdf->page, &width, &height ); poppler_page_get_size( pdf->page, &width, &height );
pdf->pages[i].left = 0; pdf->pages[i].left = 0;
pdf->pages[i].top = top; pdf->pages[i].top = top;
pdf->pages[i].width = width * pdf->scale; /* We do round to nearest, in the same way that vips_resize()
pdf->pages[i].height = height * pdf->scale; * 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 ) if( pdf->pages[i].width > pdf->image.width )
pdf->image.width = pdf->pages[i].width; pdf->image.width = pdf->pages[i].width;

@ -341,10 +341,14 @@ vips_foreign_load_pdf_header( VipsForeignLoad *load )
return( -1 ); return( -1 );
pdf->pages[i].left = 0; pdf->pages[i].left = 0;
pdf->pages[i].top = top; pdf->pages[i].top = top;
pdf->pages[i].width = /* We do round to nearest, in the same way that vips_resize()
FPDF_GetPageWidth( pdf->page ) * pdf->scale; * does round to nearest. Without this, things like
pdf->pages[i].height = * shrink-on-load will break.
FPDF_GetPageHeight( pdf->page ) * pdf->scale; */
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 ) if( pdf->pages[i].width > pdf->image.width )
pdf->image.width = pdf->pages[i].width; pdf->image.width = pdf->pages[i].width;

@ -226,14 +226,14 @@ int vips__iswebp_buffer( const void *buf, size_t len );
int vips__iswebp( const char *filename ); int vips__iswebp( const char *filename );
int vips__webp_read_file_header( const char *name, VipsImage *out, 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 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 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 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 vips__webp_write_file( VipsImage *out, const char *filename,
int Q, gboolean lossless, VipsForeignWebpPreset preset, int Q, gboolean lossless, VipsForeignWebpPreset preset,

@ -2345,25 +2345,7 @@ rtiff_new_buffer( const void *buf, size_t len, VipsImage *out,
return( rtiff ); return( rtiff );
} }
/* /* For istiffpyramid(), see vips_thumbnail_get_tiff_pyramid().
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 );
}
*/ */
int int
@ -2379,7 +2361,8 @@ vips__tiff_read( const char *filename, VipsImage *out,
vips__tiff_init(); 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 ); return( -1 );
if( rtiff->header.tiled ) { if( rtiff->header.tiled ) {
@ -2394,7 +2377,7 @@ vips__tiff_read( const char *filename, VipsImage *out,
return( 0 ); 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. * 8.
*/ */
static void static void

@ -17,6 +17,9 @@
* - could memleak on some read errors * - could memleak on some read errors
* 24/4/19 * 24/4/19
* - fix bg handling in animations * - 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; 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. /* Size of final output image.
*/ */
@ -150,6 +153,39 @@ typedef struct {
VipsRect dispose_rect; VipsRect dispose_rect;
} Read; } 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( "<unkown>" );
}
}
static void static void
vips_image_paint_area( VipsImage *image, const VipsRect *r, const VipsPel *ink ) 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 ) ); return( setRGBA( rR, gR, bR, aR ) );
} }
/* Blend sub into frame at left, top.
*/
static void static void
vips_image_paint_image( VipsImage *image, vips_image_paint_image( VipsImage *frame,
VipsImage *ink, int x, int y, gboolean blend ) VipsImage *sub, int left, int top, gboolean blend )
{ {
VipsRect valid = { 0, 0, image->Xsize, image->Ysize }; VipsRect frame_rect = { 0, 0, frame->Xsize, frame->Ysize };
VipsRect sub = { x, y, ink->Xsize, ink->Ysize }; VipsRect sub_rect = { left, top, sub->Xsize, sub->Ysize };
int ps = VIPS_IMAGE_SIZEOF_PEL( image ); int ps = VIPS_IMAGE_SIZEOF_PEL( frame );
VipsRect ovl; 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. /* Disable blend if we are not RGBA.
*/ */
if( image->Bands != 4 ) if( frame->Bands != 4 )
blend = FALSE; blend = FALSE;
vips_rect_intersectrect( &valid, &sub, &ovl ); vips_rect_intersectrect( &frame_rect, &sub_rect, &ovl );
if( !vips_rect_isempty( &ovl ) ) { if( !vips_rect_isempty( &ovl ) ) {
VipsPel *p, *q; VipsPel *p, *q;
int i; int x, y;
p = VIPS_IMAGE_ADDR( ink, ovl.left - x, ovl.top - y ); p = VIPS_IMAGE_ADDR( sub, ovl.left - left, ovl.top - top );
q = VIPS_IMAGE_ADDR( image, ovl.left, ovl.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 ) { if( blend ) {
guint32 *A = (guint32 *) p; guint32 *A = (guint32 *) p;
guint32 *B = (guint32 *) q; guint32 *B = (guint32 *) q;
@ -267,8 +305,8 @@ vips_image_paint_image( VipsImage *image,
memcpy( (char *) q, (char *) p, memcpy( (char *) q, (char *) p,
ovl.width * ps ); ovl.width * ps );
p += VIPS_IMAGE_SIZEOF_LINE( ink ); p += VIPS_IMAGE_SIZEOF_LINE( sub );
q += VIPS_IMAGE_SIZEOF_LINE( image ); q += VIPS_IMAGE_SIZEOF_LINE( frame );
} }
} }
} }
@ -325,7 +363,7 @@ read_free( Read *read )
static Read * static Read *
read_new( const char *filename, const void *data, size_t length, 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; Read *read;
@ -337,7 +375,7 @@ read_new( const char *filename, const void *data, size_t length,
read->length = length; read->length = length;
read->page = page; read->page = page;
read->n = n; read->n = n;
read->shrink = shrink; read->scale = scale;
read->delay = 100; read->delay = 100;
read->fd = 0; read->fd = 0;
read->demux = NULL; read->demux = NULL;
@ -394,14 +432,17 @@ read_header( Read *read, VipsImage *out )
canvas_width = WebPDemuxGetI( read->demux, WEBP_FF_CANVAS_WIDTH ); canvas_width = WebPDemuxGetI( read->demux, WEBP_FF_CANVAS_WIDTH );
canvas_height = WebPDemuxGetI( read->demux, WEBP_FF_CANVAS_HEIGHT ); canvas_height = WebPDemuxGetI( read->demux, WEBP_FF_CANVAS_HEIGHT );
read->frame_width = canvas_width / read->shrink; /* We round-to-nearest cf. pdfload etc.
read->frame_height = canvas_height / read->shrink; */
read->frame_width = VIPS_RINT( canvas_width * read->scale );
read->frame_height = VIPS_RINT( canvas_height * read->scale );
if( read->shrink > 1 ) { #ifdef DEBUG
read->config.options.use_scaling = 1; printf( "webp2vips: canvas_width = %d\n", canvas_width );
read->config.options.scaled_width = read->frame_width; printf( "webp2vips: canvas_height = %d\n", canvas_height );
read->config.options.scaled_height = read->frame_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 ); flags = WebPDemuxGetI( read->demux, WEBP_FF_FORMAT_FLAGS );
@ -521,11 +562,11 @@ read_header( Read *read, VipsImage *out )
int int
vips__webp_read_file_header( const char *filename, VipsImage *out, vips__webp_read_file_header( const char *filename, VipsImage *out,
int page, int n, int shrink ) int page, int n, double scale )
{ {
Read *read; 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", vips_error( "webp2vips",
_( "unable to open \"%s\"" ), filename ); _( "unable to open \"%s\"" ), filename );
return( -1 ); return( -1 );
@ -541,6 +582,9 @@ vips__webp_read_file_header( const char *filename, VipsImage *out,
return( 0 ); return( 0 );
} }
/* Read a single frame -- a width * height block of pixels. This will get
* blended into the accumulator at some offset.
*/
static VipsImage * static VipsImage *
read_frame( Read *read, read_frame( Read *read,
int width, int height, const guint8 *data, size_t length ) int width, int height, const guint8 *data, size_t length )
@ -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.rgba = VIPS_IMAGE_ADDR( frame, 0, 0 );
read->config.output.u.RGBA.stride = VIPS_IMAGE_SIZEOF_LINE( frame ); read->config.output.u.RGBA.stride = VIPS_IMAGE_SIZEOF_LINE( frame );
read->config.output.u.RGBA.size = VIPS_IMAGE_SIZEOF_IMAGE( 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 ) { if( WebPDecode( data, length, &read->config ) != VP8_STATUS_OK ) {
g_object_unref( frame ); g_object_unref( frame );
@ -582,16 +631,26 @@ static int
read_next_frame( Read *read ) read_next_frame( Read *read )
{ {
VipsImage *frame; VipsImage *frame;
VipsRect area;
#ifdef DEBUG #ifdef DEBUG
printf( "read_next_frame:\n" ); printf( "read_next_frame:\n" );
#endif /*DEBUG*/ #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. /* Dispose from the previous frame.
*/ */
if( read->dispose_method == WEBP_MUX_DISPOSE_BACKGROUND ) { if( read->dispose_method == WEBP_MUX_DISPOSE_BACKGROUND ) {
/* We must clear the pixels occupied by this webp frame (not /* We must clear the pixels occupied by the previous webp
* the whole of the read frame) to 0 (transparent). * frame (not the whole of the read frame) to 0 (transparent).
* *
* We do not clear to WEBP_FF_BACKGROUND_COLOR. That's only * We do not clear to WEBP_FF_BACKGROUND_COLOR. That's only
* used to composite down to RGB. Perhaps we * 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. /* Note this frame's dispose for next time.
*/ */
read->dispose_method = read->iter.dispose_method; read->dispose_method = read->iter.dispose_method;
read->dispose_rect.left = read->iter.x_offset; read->dispose_rect = area;
read->dispose_rect.top = read->iter.y_offset;
read->dispose_rect.width = read->iter.width;
read->dispose_rect.height = read->iter.height;
#ifdef DEBUG #ifdef DEBUG
printf( "webp2vips: frame_num = %d\n", read->iter.frame_num ); printf( "webp2vips: frame_num = %d\n", read->iter.frame_num );
printf( " x_offset = %d\n", read->iter.x_offset ); printf( " left = %d\n", area.left );
printf( " y_offset = %d\n", read->iter.y_offset ); printf( " top = %d\n", area.top );
printf( " width = %d\n", read->iter.width ); printf( " width = %d\n", area.width );
printf( " height = %d\n", read->iter.height ); printf( " height = %d\n", area.height );
printf( " duration = %d\n", read->iter.duration ); printf( " duration = %d\n", read->iter.duration );
printf( " dispose = " ); printf( " dispose = " );
if( read->iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND ) if( read->iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND )
@ -637,14 +693,14 @@ read_next_frame( Read *read )
"not all frames have equal duration" ); "not all frames have equal duration" );
if( !(frame = read_frame( read, if( !(frame = read_frame( read,
read->iter.width, read->iter.height, area.width, area.height,
read->iter.fragment.bytes, read->iter.fragment.size )) ) read->iter.fragment.bytes, read->iter.fragment.size )) )
return( -1 ); return( -1 );
/* Now blend or copy the new pixels into our accumulator. /* Now blend or copy the new pixels into our accumulator.
*/ */
vips_image_paint_image( read->frame, frame, 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 ); read->iter.blend_method == WEBP_MUX_BLEND );
g_object_unref( frame ); g_object_unref( frame );
@ -715,11 +771,11 @@ read_image( Read *read, VipsImage *out )
int int
vips__webp_read_file( const char *filename, VipsImage *out, vips__webp_read_file( const char *filename, VipsImage *out,
int page, int n, int shrink ) int page, int n, double scale )
{ {
Read *read; 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", vips_error( "webp2vips",
_( "unable to open \"%s\"" ), filename ); _( "unable to open \"%s\"" ), filename );
return( -1 ); return( -1 );
@ -737,11 +793,11 @@ vips__webp_read_file( const char *filename, VipsImage *out,
int int
vips__webp_read_buffer_header( const void *buf, size_t len, VipsImage *out, 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; 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", vips_error( "webp2vips",
"%s", _( "unable to open buffer" ) ); "%s", _( "unable to open buffer" ) );
return( -1 ); return( -1 );
@ -759,11 +815,11 @@ vips__webp_read_buffer_header( const void *buf, size_t len, VipsImage *out,
int int
vips__webp_read_buffer( const void *buf, size_t len, VipsImage *out, 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; 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", vips_error( "webp2vips",
"%s", _( "unable to open buffer" ) ); "%s", _( "unable to open buffer" ) );
return( -1 ); return( -1 );

@ -6,6 +6,8 @@
* - add @shrink * - add @shrink
* 1/11/18 * 1/11/18
* - add @page, @n * - add @page, @n
* 30/4/19
* - deprecate @shrink, use @scale instead
*/ */
/* /*
@ -64,7 +66,11 @@ typedef struct _VipsForeignLoadWebp {
*/ */
int n; int n;
/* Shrink by this much during load. /* Scale by this much during load.
*/
double scale;
/* Old and deprecated scaling path.
*/ */
int shrink; int shrink;
} VipsForeignLoadWebp; } VipsForeignLoadWebp;
@ -120,10 +126,20 @@ vips_foreign_load_webp_class_init( VipsForeignLoadWebpClass *class )
G_STRUCT_OFFSET( VipsForeignLoadWebp, n ), G_STRUCT_OFFSET( VipsForeignLoadWebp, n ),
-1, 100000, 1 ); -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" ),
_( "Shrink factor on load" ), _( "Shrink factor on load" ),
VIPS_ARGUMENT_OPTIONAL_INPUT, VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
G_STRUCT_OFFSET( VipsForeignLoadWebp, shrink ), G_STRUCT_OFFSET( VipsForeignLoadWebp, shrink ),
1, 1024, 1 ); 1, 1024, 1 );
@ -134,6 +150,7 @@ vips_foreign_load_webp_init( VipsForeignLoadWebp *webp )
{ {
webp->n = 1; webp->n = 1;
webp->shrink = 1; webp->shrink = 1;
webp->scale = 1.0;
} }
typedef struct _VipsForeignLoadWebpFile { typedef struct _VipsForeignLoadWebpFile {
@ -168,8 +185,15 @@ vips_foreign_load_webp_file_header( VipsForeignLoad *load )
VipsForeignLoadWebp *webp = (VipsForeignLoadWebp *) load; VipsForeignLoadWebp *webp = (VipsForeignLoadWebp *) load;
VipsForeignLoadWebpFile *file = (VipsForeignLoadWebpFile *) 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, if( vips__webp_read_file_header( file->filename, load->out,
webp->page, webp->n, webp->shrink ) ) webp->page, webp->n, webp->scale ) )
return( -1 ); return( -1 );
VIPS_SETSTR( load->out->filename, file->filename ); VIPS_SETSTR( load->out->filename, file->filename );
@ -184,7 +208,7 @@ vips_foreign_load_webp_file_load( VipsForeignLoad *load )
VipsForeignLoadWebpFile *file = (VipsForeignLoadWebpFile *) load; VipsForeignLoadWebpFile *file = (VipsForeignLoadWebpFile *) load;
if( vips__webp_read_file( file->filename, load->real, if( vips__webp_read_file( file->filename, load->real,
webp->page, webp->n, webp->shrink ) ) webp->page, webp->n, webp->scale ) )
return( -1 ); return( -1 );
return( 0 ); return( 0 );
@ -249,7 +273,7 @@ vips_foreign_load_webp_buffer_header( VipsForeignLoad *load )
if( vips__webp_read_buffer_header( buffer->buf->data, if( vips__webp_read_buffer_header( buffer->buf->data,
buffer->buf->length, load->out, buffer->buf->length, load->out,
webp->page, webp->n, webp->shrink ) ) webp->page, webp->n, webp->scale ) )
return( -1 ); return( -1 );
return( 0 ); 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, if( vips__webp_read_buffer( buffer->buf->data, buffer->buf->length,
load->real, load->real,
webp->page, webp->n, webp->shrink ) ) webp->page, webp->n, webp->scale ) )
return( -1 ); return( -1 );
return( 0 ); return( 0 );
@ -317,7 +341,7 @@ vips_foreign_load_webp_buffer_init( VipsForeignLoadWebpBuffer *buffer )
* *
* * @page: %gint, page (frame) to read * * @page: %gint, page (frame) to read
* * @n: %gint, load this many pages * * @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. * 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() * left. Set to -1 to mean "until the end of the document". Use vips_grid()
* to change page layout. * 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. * 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 * * @page: %gint, page (frame) to read
* * @n: %gint, load this many pages * * @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 * Read a WebP-formatted memory block into a VIPS image. Exactly as
* vips_webpload(), but read from a memory buffer. * vips_webpload(), but read from a memory buffer.

@ -635,7 +635,7 @@ write_vips( VipsRegion *region, VipsRect *area, void *a, void *b )
size_t nwritten, count; size_t nwritten, count;
void *buf; void *buf;
count = region->bpl * area->height; count = (size_t) region->bpl * area->height;
buf = VIPS_REGION_ADDR( region, 0, area->top ); buf = VIPS_REGION_ADDR( region, 0, area->top );
do { do {

@ -109,17 +109,16 @@ typedef struct _VipsThumbnail {
int input_height; int input_height;
int page_height; int page_height;
VipsAngle angle; /* From vips_autorot_get_angle() */ 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. /* 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_count;
int level_width[MAX_LEVELS]; int level_width[MAX_LEVELS];
int level_height[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. /* For HEIF, try to fetch the size of the stored thumbnail.
*/ */
int heif_thumbnail_width; int heif_thumbnail_width;
@ -191,14 +190,16 @@ vips_thumbnail_read_header( VipsThumbnail *thumbnail, VipsImage *image )
thumbnail->input_width = image->Xsize; thumbnail->input_width = image->Xsize;
thumbnail->input_height = image->Ysize; thumbnail->input_height = image->Ysize;
thumbnail->angle = vips_autorot_get_angle( image ); 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 ) ) { /* The "n-pages" metadata item is the number of pages in the document,
int n_pages; * not the number we've read out into this image. We calculate
* ourselves from page_height.
if( !vips_image_get_int( image, VIPS_META_N_PAGES, &n_pages ) ) *
thumbnail->n_pages = * vips_image_get_page_height() verifies that Ysize is a simple
VIPS_CLIP( 1, n_pages, MAX_LEVELS ); * multiple of page_height.
} */
thumbnail->n_pages = thumbnail->input_height / thumbnail->page_height;
/* For openslide, read out the level structure too. /* 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. /* 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 static void
vips_thumbnail_get_tiff_pyramid( VipsThumbnail *thumbnail ) 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 ); VipsThumbnailClass *class = VIPS_THUMBNAIL_GET_CLASS( thumbnail );
int i; int i;
/* Only one page? Can't be.
*/
if( thumbnail->n_pages < 2 )
return;
for( i = 0; i < thumbnail->n_pages; i++ ) { for( i = 0; i < thumbnail->n_pages; i++ ) {
VipsImage *page; VipsImage *page;
int level_width; int level_width;
@ -246,13 +253,10 @@ vips_thumbnail_get_tiff_pyramid( VipsThumbnail *thumbnail )
level_height = page->Ysize; level_height = page->Ysize;
VIPS_UNREF( page ); 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_width = thumbnail->input_width / (1 << i);
expected_level_height = thumbnail->input_height / (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 || if( abs( level_width - expected_level_width ) > 5 ||
level_width < 2 ) level_width < 2 )
@ -405,7 +409,7 @@ vips_thumbnail_find_jpegshrink( VipsThumbnail *thumbnail,
return( 1 ); return( 1 );
} }
/* Find the best openslide level. /* Find the best pyramid (openslide or tiff) level.
*/ */
static int static int
vips_thumbnail_find_pyrlevel( VipsThumbnail *thumbnail, vips_thumbnail_find_pyrlevel( VipsThumbnail *thumbnail,
@ -444,7 +448,7 @@ 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 );
/* 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. * get all the pyramid levels.
*/ */
if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ) if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) )
@ -456,6 +460,10 @@ vips_thumbnail_open( VipsThumbnail *thumbnail )
if( vips_isprefix( "VipsForeignLoadHeif", thumbnail->loader ) ) if( vips_isprefix( "VipsForeignLoadHeif", thumbnail->loader ) )
vips_thumbnail_get_heif_thumb_info( thumbnail ); vips_thumbnail_get_heif_thumb_info( thumbnail );
/* We read the openslide level structure in
* vips_thumbnail_read_header().
*/
factor = 1.0; factor = 1.0;
if( vips_isprefix( "VipsForeignLoadJpeg", thumbnail->loader ) ) { 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 ); g_info( "loading jpeg with factor %g pre-shrink", factor );
} }
else if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) || else if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ||
vips_isprefix( "VipsForeignLoadOpenslide", thumbnail->loader ) ) { vips_isprefix( "VipsForeignLoadOpenslide",
thumbnail->loader ) ) {
factor = vips_thumbnail_find_pyrlevel( thumbnail, factor = vips_thumbnail_find_pyrlevel( thumbnail,
thumbnail->input_width, thumbnail->input_height ); thumbnail->input_width, thumbnail->input_height );
g_info( "loading pyr level %g", factor ); g_info( "loading pyr level %g", factor );
} }
else if( vips_isprefix( "VipsForeignLoadPdf", thumbnail->loader ) || else if( vips_isprefix( "VipsForeignLoadPdf", thumbnail->loader ) ) {
vips_isprefix( "VipsForeignLoadSvg", 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 / factor = 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 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 ) ) { else if( vips_isprefix( "VipsForeignLoadHeif", thumbnail->loader ) ) {
/* 'factor' is a gboolean which enables thumbnail load instead /* 'factor' is a gboolean which enables thumbnail load instead
@ -493,24 +509,14 @@ vips_thumbnail_open( VipsThumbnail *thumbnail )
factor = 0.0; 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 ) ) { else if( vips_isprefix( "VipsForeignLoadWebp", thumbnail->loader ) ) {
factor = VIPS_MAX( 1.0, factor = 1.0 /
vips_thumbnail_calculate_common_shrink( thumbnail, vips_thumbnail_calculate_common_shrink( thumbnail,
thumbnail->input_width, 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 )) ) if( !(im = class->open( thumbnail, factor )) )
return( NULL ); return( NULL );
@ -529,6 +535,8 @@ vips_thumbnail_build( VipsObject *object )
VIPS_INTERPRETATION_scRGB : VIPS_INTERPRETATION_sRGB; VIPS_INTERPRETATION_scRGB : VIPS_INTERPRETATION_sRGB;
VipsImage *in; VipsImage *in;
int preshrunk_page_height;
int output_page_height;
double hshrink; double hshrink;
double vshrink; double vshrink;
@ -569,10 +577,9 @@ vips_thumbnail_build( VipsObject *object )
return( -1 ); return( -1 );
in = t[0]; in = t[0];
/* So page_height is after pre-shrink, but before the main shrink /* After pre-shrink, but before the main shrink stage.
* stage.
*/ */
thumbnail->page_height = vips_image_get_page_height( in ); preshrunk_page_height = vips_image_get_page_height( in );
/* RAD needs special unpacking. /* RAD needs special unpacking.
*/ */
@ -653,17 +660,17 @@ vips_thumbnail_build( VipsObject *object )
in = t[3]; 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, 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 /* 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( int target_page_height = VIPS_RINT(
thumbnail->page_height / vshrink ); preshrunk_page_height / vshrink );
int target_image_height = target_page_height * int target_image_height = target_page_height *
thumbnail->n_pages; thumbnail->n_pages;
@ -676,8 +683,9 @@ vips_thumbnail_build( VipsObject *object )
return( -1 ); return( -1 );
in = t[4]; in = t[4];
thumbnail->page_height = VIPS_RINT( thumbnail->page_height / vshrink ); output_page_height = VIPS_RINT( preshrunk_page_height / vshrink );
vips_image_set_int( in, VIPS_META_PAGE_HEIGHT, thumbnail->page_height ); vips_image_set_int( in,
VIPS_META_PAGE_HEIGHT, output_page_height );
if( have_premultiplied ) { if( have_premultiplied ) {
g_info( "unpremultiplying alpha" ); g_info( "unpremultiplying alpha" );
@ -942,7 +950,8 @@ vips_thumbnail_file_open( VipsThumbnail *thumbnail, double factor )
NULL ) ); NULL ) );
} }
else if( vips_isprefix( "VipsForeignLoadPdf", thumbnail->loader ) || 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, return( vips_image_new_from_file( file->filename,
"access", VIPS_ACCESS_SEQUENTIAL, "access", VIPS_ACCESS_SEQUENTIAL,
"scale", factor, "scale", factor,
@ -1137,7 +1146,8 @@ vips_thumbnail_buffer_open( VipsThumbnail *thumbnail, double factor )
NULL ) ); NULL ) );
} }
else if( vips_isprefix( "VipsForeignLoadPdf", thumbnail->loader ) || 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( return( vips_image_new_from_buffer(
buffer->buf->data, buffer->buf->length, buffer->buf->data, buffer->buf->length,
buffer->option_string, buffer->option_string,

@ -597,7 +597,7 @@ class TestForeign:
def pdf_valid(im): def pdf_valid(im):
a = im(10, 10) a = im(10, 10)
assert_almost_equal_objects(a, [35, 31, 32, 255]) assert_almost_equal_objects(a, [35, 31, 32, 255])
assert im.width == 1133 assert im.width == 1134
assert im.height == 680 assert im.height == 680
assert im.bands == 4 assert im.bands == 4