From 2d0c6b364cd75a760a03b120ae024f5ca7bf8d1f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 6 Jun 2022 12:08:43 +0100 Subject: [PATCH] improve scaling of SVG images We were not scaling images with fixed absolute dimensions. --- libvips/foreign/svgload.c | 68 ++++++++++++++-------------------- libvips/iofuncs/ginputsource.c | 30 +++++++-------- libvips/iofuncs/init.c | 30 +++++++-------- 3 files changed, 57 insertions(+), 71 deletions(-) diff --git a/libvips/foreign/svgload.c b/libvips/foreign/svgload.c index d3861606..130ef38e 100644 --- a/libvips/foreign/svgload.c +++ b/libvips/foreign/svgload.c @@ -377,8 +377,8 @@ svg_css_length_to_pixels( RsvgLength length, double dpi ) value = dpi * value / 6; break; default: - /* Probably RSVG_UNIT_PERCENT. We can't know what the pixel - * value is without more information. + /* Probably RSVG_UNIT_PERCENT. We can't know what the + * pixel value is without more information. */ value = 0; } @@ -397,6 +397,7 @@ vips_foreign_load_svg_get_natural_size( VipsForeignLoadSvg *svg, double height; #if LIBRSVG_CHECK_VERSION( 2, 52, 0 ) + if( !rsvg_handle_get_intrinsic_size_in_pixels( svg->page, &width, &height ) ) { RsvgRectangle zero_rect, viewbox; @@ -412,11 +413,11 @@ vips_foreign_load_svg_get_natural_size( VipsForeignLoadSvg *svg, &has_height, &iheight, &has_viewbox, &viewbox ); - /* After librsvg 2.54.0, the `has_width` and `has_height` arguments - * always returns `TRUE`, since with SVG2 all documents *have* a - * default width and height of `100%`. - */ #if LIBRSVG_CHECK_VERSION( 2, 54, 0 ) + /* After librsvg 2.54.0, the `has_width` and `has_height` + * arguments always returns `TRUE`, since with SVG2 all + * documents *have* a default width and height of `100%`. + */ width = svg_css_length_to_pixels( iwidth, svg->dpi ); height = svg_css_length_to_pixels( iheight, svg->dpi ); @@ -424,7 +425,8 @@ vips_foreign_load_svg_get_natural_size( VipsForeignLoadSvg *svg, has_height = height > 0.0; if( has_width && has_height ) { - /* Success! Taking the viewbox into account is not needed. + /* Success! Taking the viewbox into account is not + * needed. */ } else if( has_width && has_viewbox ) { @@ -460,8 +462,8 @@ vips_foreign_load_svg_get_natural_size( VipsForeignLoadSvg *svg, if( width <= 0.0 || height <= 0.0 ) { - /* We haven't found a usable set of sizes, so try working out - * the visible area. + /* We haven't found a usable set of sizes, so try + * working out the visible area. */ rsvg_handle_get_geometry_for_layer( svg->page, NULL, &zero_rect, &viewbox, NULL, NULL ); @@ -469,7 +471,9 @@ vips_foreign_load_svg_get_natural_size( VipsForeignLoadSvg *svg, height = viewbox.y + viewbox.height; } } + #else /*!LIBRSVG_CHECK_VERSION( 2, 52, 0 )*/ + { RsvgDimensionData dimensions; @@ -477,6 +481,7 @@ vips_foreign_load_svg_get_natural_size( VipsForeignLoadSvg *svg, width = dimensions.width; height = dimensions.height; } + #endif /*LIBRSVG_CHECK_VERSION( 2, 52, 0 )*/ /* width or height below 0.5 can't be rounded to 1. @@ -499,7 +504,6 @@ vips_foreign_load_svg_get_scaled_size( VipsForeignLoadSvg *svg, { double width; double height; - double scale; /* Get dimensions with the default dpi. */ @@ -507,36 +511,12 @@ vips_foreign_load_svg_get_scaled_size( VipsForeignLoadSvg *svg, if( vips_foreign_load_svg_get_natural_size( svg, &width, &height ) ) return( -1 ); - /* Calculate dimensions at required dpi/scale. + /* We scale up with cairo --- scaling with rsvg_handle_set_dpi() will + * fail for SVGs with absolute sizes. */ - scale = svg->scale * svg->dpi / 72.0; - if( scale != 1.0 ) { - double scaled_width; - double scaled_height; - - rsvg_handle_set_dpi( svg->page, scale * 72.0 ); - if( vips_foreign_load_svg_get_natural_size( svg, - &scaled_width, &scaled_height ) ) - return( -1 ); - - if( scaled_width == width && - scaled_height == height ) { - /* SVG without width and height always reports the same - * dimensions regardless of dpi. Apply dpi/scale using - * cairo instead. - */ - svg->cairo_scale = scale; - width *= scale; - height *= scale; - } - else { - /* SVG with width and height reports correctly scaled - * dimensions. - */ - width = scaled_width; - height = scaled_height; - } - } + svg->cairo_scale = svg->scale * svg->dpi / 72.0; + width *= svg->cairo_scale; + height *= svg->cairo_scale; *out_width = VIPS_ROUND_UINT( width ); *out_height = VIPS_ROUND_UINT( height ); @@ -563,7 +543,8 @@ vips_foreign_load_svg_parse( VipsForeignLoadSvg *svg, VipsImage *out ) 4, VIPS_FORMAT_UCHAR, VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, res, res ); - /* We render to a linecache, so fat strips work well. + /* We render to a cache with a couple of rows of tiles, so fat strips + * work well. */ if( vips_image_pipelinev( out, VIPS_DEMAND_STYLE_FATSTRIP, NULL ) ) return( -1 ); @@ -613,10 +594,14 @@ vips_foreign_load_svg_generate( VipsRegion *or, * running inside a non-threaded tilecache. */ #if LIBRSVG_CHECK_VERSION( 2, 46, 0 ) + { RsvgRectangle viewport; GError *error = NULL; + /* No need to scale -- we always set the viewport to the + * whole image, and set the region to draw on the surface. + */ cairo_translate( cr, -r->left, -r->top ); viewport.x = 0; viewport.y = 0; @@ -634,7 +619,9 @@ vips_foreign_load_svg_generate( VipsRegion *or, cairo_destroy( cr ); } + #else /*!LIBRSVG_CHECK_VERSION( 2, 46, 0 )*/ + cairo_scale( cr, svg->cairo_scale, svg->cairo_scale ); cairo_translate( cr, -r->left / svg->cairo_scale, -r->top / svg->cairo_scale ); @@ -648,6 +635,7 @@ vips_foreign_load_svg_generate( VipsRegion *or, } cairo_destroy( cr ); + #endif /*LIBRSVG_CHECK_VERSION( 2, 46, 0 )*/ /* Cairo makes pre-multipled BRGA -- we must byteswap and unpremultiply. diff --git a/libvips/iofuncs/ginputsource.c b/libvips/iofuncs/ginputsource.c index f318381d..dee229cc 100644 --- a/libvips/iofuncs/ginputsource.c +++ b/libvips/iofuncs/ginputsource.c @@ -99,7 +99,7 @@ vips_g_input_stream_finalize( GObject *object ) { VipsGInputStream *gstream = VIPS_G_INPUT_STREAM( object ); - VIPS_FREEF( g_object_unref, gstream->source ); + VIPS_UNREF( gstream->source ); G_OBJECT_CLASS( vips_g_input_stream_parent_class )->finalize( object ); } @@ -107,13 +107,13 @@ vips_g_input_stream_finalize( GObject *object ) static goffset vips_g_input_stream_tell( GSeekable *seekable ) { - VipsSource *source = VIPS_G_INPUT_STREAM( seekable )->source; + VipsGInputStream *gstream = VIPS_G_INPUT_STREAM( seekable ); goffset pos; VIPS_DEBUG_MSG( "vips_g_input_stream_tell:\n" ); - pos = vips_source_seek( source, 0, SEEK_CUR ); + pos = vips_source_seek( gstream->source, 0, SEEK_CUR ); if( pos == -1 ) return( 0 ); @@ -123,12 +123,12 @@ vips_g_input_stream_tell( GSeekable *seekable ) static gboolean vips_g_input_stream_can_seek( GSeekable *seekable ) { - VipsSource *source = VIPS_G_INPUT_STREAM( seekable )->source; + VipsGInputStream *gstream = VIPS_G_INPUT_STREAM( seekable ); VIPS_DEBUG_MSG( "vips_g_input_stream_can_seek: %d\n", - !source->is_pipe ); + !gstream->source->is_pipe ); - return( !source->is_pipe ); + return( !gstream->source->is_pipe ); } static int @@ -149,12 +149,12 @@ static gboolean vips_g_input_stream_seek( GSeekable *seekable, goffset offset, GSeekType type, GCancellable *cancellable, GError **error ) { - VipsSource *source = VIPS_G_INPUT_STREAM( seekable )->source; + VipsGInputStream *gstream = VIPS_G_INPUT_STREAM( seekable ); VIPS_DEBUG_MSG( "vips_g_input_stream_seek: offset = %" G_GINT64_FORMAT ", type = %d\n", offset, type ); - if( vips_source_seek( source, offset, + if( vips_source_seek( gstream->source, offset, seek_type_to_lseek( type ) ) == -1 ) { g_set_error( error, G_IO_ERROR, G_IO_ERROR_FAILED, @@ -189,17 +189,16 @@ static gssize vips_g_input_stream_read( GInputStream *stream, void *buffer, gsize count, GCancellable *cancellable, GError **error ) { - VipsSource *source; - gssize res; + VipsGInputStream *gstream = VIPS_G_INPUT_STREAM( stream ); - source = VIPS_G_INPUT_STREAM( stream )->source; + gssize res; VIPS_DEBUG_MSG( "vips_g_input_stream_read: count: %zd\n", count ); if( g_cancellable_set_error_if_cancelled( cancellable, error ) ) return( -1 ); - if( (res = vips_source_read( source, buffer, count )) == -1 ) + if( (res = vips_source_read( gstream->source, buffer, count )) == -1 ) g_set_error( error, G_IO_ERROR, G_IO_ERROR_FAILED, _( "Error while reading: %s" ), @@ -212,17 +211,16 @@ static gssize vips_g_input_stream_skip( GInputStream *stream, gsize count, GCancellable *cancellable, GError **error ) { - VipsSource *source; - gssize position; + VipsGInputStream *gstream = VIPS_G_INPUT_STREAM( stream ); - source = VIPS_G_INPUT_STREAM( stream )->source; + gssize position; VIPS_DEBUG_MSG( "vips_g_input_stream_skip: count: %zd\n", count ); if( g_cancellable_set_error_if_cancelled( cancellable, error ) ) return( -1 ); - position = vips_source_seek( source, count, SEEK_CUR ); + position = vips_source_seek( gstream->source, count, SEEK_CUR ); if( position == -1 ) { g_set_error( error, G_IO_ERROR, G_IO_ERROR_FAILED, diff --git a/libvips/iofuncs/init.c b/libvips/iofuncs/init.c index d758824b..a3988ae8 100644 --- a/libvips/iofuncs/init.c +++ b/libvips/iofuncs/init.c @@ -742,21 +742,6 @@ vips_shutdown( void ) vips_cache_drop_all(); - /* In dev releases, always show leaks. But not more than once, it's - * annoying. - */ -#ifndef DEBUG_LEAK - if( vips__leak ) -#endif /*DEBUG_LEAK*/ - { - static gboolean done = FALSE; - - if( !done ) { - done = TRUE; - vips_leak(); - } - } - #if ENABLE_DEPRECATED im_close_plugins(); #endif @@ -787,6 +772,21 @@ vips_shutdown( void ) VIPS_FREE( vips__argv0 ); VIPS_FREE( vips__prgname ); VIPS_FREEF( g_timer_destroy, vips__global_timer ); + + /* In dev releases, always show leaks. But not more than once, it's + * annoying. + */ +#ifndef DEBUG_LEAK + if( vips__leak ) +#endif /*DEBUG_LEAK*/ + { + static gboolean done = FALSE; + + if( !done ) { + done = TRUE; + vips_leak(); + } + } } static gboolean