improve scaling of SVG images

We were not scaling images with fixed absolute dimensions.
This commit is contained in:
John Cupitt 2022-06-06 12:08:43 +01:00
parent 586fb31550
commit 2d0c6b364c
3 changed files with 57 additions and 71 deletions

View File

@ -377,8 +377,8 @@ svg_css_length_to_pixels( RsvgLength length, double dpi )
value = dpi * value / 6; value = dpi * value / 6;
break; break;
default: default:
/* Probably RSVG_UNIT_PERCENT. We can't know what the pixel /* Probably RSVG_UNIT_PERCENT. We can't know what the
* value is without more information. * pixel value is without more information.
*/ */
value = 0; value = 0;
} }
@ -397,6 +397,7 @@ vips_foreign_load_svg_get_natural_size( VipsForeignLoadSvg *svg,
double height; double height;
#if LIBRSVG_CHECK_VERSION( 2, 52, 0 ) #if LIBRSVG_CHECK_VERSION( 2, 52, 0 )
if( !rsvg_handle_get_intrinsic_size_in_pixels( svg->page, if( !rsvg_handle_get_intrinsic_size_in_pixels( svg->page,
&width, &height ) ) { &width, &height ) ) {
RsvgRectangle zero_rect, viewbox; RsvgRectangle zero_rect, viewbox;
@ -412,11 +413,11 @@ vips_foreign_load_svg_get_natural_size( VipsForeignLoadSvg *svg,
&has_height, &iheight, &has_height, &iheight,
&has_viewbox, &viewbox ); &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 ) #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 ); width = svg_css_length_to_pixels( iwidth, svg->dpi );
height = svg_css_length_to_pixels( iheight, 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; has_height = height > 0.0;
if( has_width && has_height ) { 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 ) { else if( has_width && has_viewbox ) {
@ -460,8 +462,8 @@ vips_foreign_load_svg_get_natural_size( VipsForeignLoadSvg *svg,
if( width <= 0.0 || if( width <= 0.0 ||
height <= 0.0 ) { height <= 0.0 ) {
/* We haven't found a usable set of sizes, so try working out /* We haven't found a usable set of sizes, so try
* the visible area. * working out the visible area.
*/ */
rsvg_handle_get_geometry_for_layer( svg->page, NULL, rsvg_handle_get_geometry_for_layer( svg->page, NULL,
&zero_rect, &viewbox, NULL, NULL ); &zero_rect, &viewbox, NULL, NULL );
@ -469,7 +471,9 @@ vips_foreign_load_svg_get_natural_size( VipsForeignLoadSvg *svg,
height = viewbox.y + viewbox.height; height = viewbox.y + viewbox.height;
} }
} }
#else /*!LIBRSVG_CHECK_VERSION( 2, 52, 0 )*/ #else /*!LIBRSVG_CHECK_VERSION( 2, 52, 0 )*/
{ {
RsvgDimensionData dimensions; RsvgDimensionData dimensions;
@ -477,6 +481,7 @@ vips_foreign_load_svg_get_natural_size( VipsForeignLoadSvg *svg,
width = dimensions.width; width = dimensions.width;
height = dimensions.height; height = dimensions.height;
} }
#endif /*LIBRSVG_CHECK_VERSION( 2, 52, 0 )*/ #endif /*LIBRSVG_CHECK_VERSION( 2, 52, 0 )*/
/* width or height below 0.5 can't be rounded to 1. /* 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 width;
double height; double height;
double scale;
/* Get dimensions with the default dpi. /* 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 ) ) if( vips_foreign_load_svg_get_natural_size( svg, &width, &height ) )
return( -1 ); 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; svg->cairo_scale = svg->scale * svg->dpi / 72.0;
if( scale != 1.0 ) { width *= svg->cairo_scale;
double scaled_width; height *= svg->cairo_scale;
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;
}
}
*out_width = VIPS_ROUND_UINT( width ); *out_width = VIPS_ROUND_UINT( width );
*out_height = VIPS_ROUND_UINT( height ); *out_height = VIPS_ROUND_UINT( height );
@ -563,7 +543,8 @@ vips_foreign_load_svg_parse( VipsForeignLoadSvg *svg, VipsImage *out )
4, VIPS_FORMAT_UCHAR, 4, VIPS_FORMAT_UCHAR,
VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, res, res ); 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 ) ) if( vips_image_pipelinev( out, VIPS_DEMAND_STYLE_FATSTRIP, NULL ) )
return( -1 ); return( -1 );
@ -613,10 +594,14 @@ vips_foreign_load_svg_generate( VipsRegion *or,
* running inside a non-threaded tilecache. * running inside a non-threaded tilecache.
*/ */
#if LIBRSVG_CHECK_VERSION( 2, 46, 0 ) #if LIBRSVG_CHECK_VERSION( 2, 46, 0 )
{ {
RsvgRectangle viewport; RsvgRectangle viewport;
GError *error = NULL; 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 ); cairo_translate( cr, -r->left, -r->top );
viewport.x = 0; viewport.x = 0;
viewport.y = 0; viewport.y = 0;
@ -634,7 +619,9 @@ vips_foreign_load_svg_generate( VipsRegion *or,
cairo_destroy( cr ); cairo_destroy( cr );
} }
#else /*!LIBRSVG_CHECK_VERSION( 2, 46, 0 )*/ #else /*!LIBRSVG_CHECK_VERSION( 2, 46, 0 )*/
cairo_scale( cr, svg->cairo_scale, svg->cairo_scale ); cairo_scale( cr, svg->cairo_scale, svg->cairo_scale );
cairo_translate( cr, -r->left / svg->cairo_scale, cairo_translate( cr, -r->left / svg->cairo_scale,
-r->top / svg->cairo_scale ); -r->top / svg->cairo_scale );
@ -648,6 +635,7 @@ vips_foreign_load_svg_generate( VipsRegion *or,
} }
cairo_destroy( cr ); cairo_destroy( cr );
#endif /*LIBRSVG_CHECK_VERSION( 2, 46, 0 )*/ #endif /*LIBRSVG_CHECK_VERSION( 2, 46, 0 )*/
/* Cairo makes pre-multipled BRGA -- we must byteswap and unpremultiply. /* Cairo makes pre-multipled BRGA -- we must byteswap and unpremultiply.

View File

@ -99,7 +99,7 @@ vips_g_input_stream_finalize( GObject *object )
{ {
VipsGInputStream *gstream = VIPS_G_INPUT_STREAM( 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 ); G_OBJECT_CLASS( vips_g_input_stream_parent_class )->finalize( object );
} }
@ -107,13 +107,13 @@ vips_g_input_stream_finalize( GObject *object )
static goffset static goffset
vips_g_input_stream_tell( GSeekable *seekable ) vips_g_input_stream_tell( GSeekable *seekable )
{ {
VipsSource *source = VIPS_G_INPUT_STREAM( seekable )->source; VipsGInputStream *gstream = VIPS_G_INPUT_STREAM( seekable );
goffset pos; goffset pos;
VIPS_DEBUG_MSG( "vips_g_input_stream_tell:\n" ); 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 ) if( pos == -1 )
return( 0 ); return( 0 );
@ -123,12 +123,12 @@ vips_g_input_stream_tell( GSeekable *seekable )
static gboolean static gboolean
vips_g_input_stream_can_seek( GSeekable *seekable ) 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", 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 static int
@ -149,12 +149,12 @@ static gboolean
vips_g_input_stream_seek( GSeekable *seekable, goffset offset, vips_g_input_stream_seek( GSeekable *seekable, goffset offset,
GSeekType type, GCancellable *cancellable, GError **error ) 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 VIPS_DEBUG_MSG( "vips_g_input_stream_seek: offset = %" G_GINT64_FORMAT
", type = %d\n", offset, type ); ", type = %d\n", offset, type );
if( vips_source_seek( source, offset, if( vips_source_seek( gstream->source, offset,
seek_type_to_lseek( type ) ) == -1 ) { seek_type_to_lseek( type ) ) == -1 ) {
g_set_error( error, G_IO_ERROR, g_set_error( error, G_IO_ERROR,
G_IO_ERROR_FAILED, G_IO_ERROR_FAILED,
@ -189,17 +189,16 @@ static gssize
vips_g_input_stream_read( GInputStream *stream, void *buffer, gsize count, vips_g_input_stream_read( GInputStream *stream, void *buffer, gsize count,
GCancellable *cancellable, GError **error ) GCancellable *cancellable, GError **error )
{ {
VipsSource *source; VipsGInputStream *gstream = VIPS_G_INPUT_STREAM( stream );
gssize res;
source = VIPS_G_INPUT_STREAM( stream )->source; gssize res;
VIPS_DEBUG_MSG( "vips_g_input_stream_read: count: %zd\n", count ); VIPS_DEBUG_MSG( "vips_g_input_stream_read: count: %zd\n", count );
if( g_cancellable_set_error_if_cancelled( cancellable, error ) ) if( g_cancellable_set_error_if_cancelled( cancellable, error ) )
return( -1 ); 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_set_error( error, G_IO_ERROR,
G_IO_ERROR_FAILED, G_IO_ERROR_FAILED,
_( "Error while reading: %s" ), _( "Error while reading: %s" ),
@ -212,17 +211,16 @@ static gssize
vips_g_input_stream_skip( GInputStream *stream, gsize count, vips_g_input_stream_skip( GInputStream *stream, gsize count,
GCancellable *cancellable, GError **error ) GCancellable *cancellable, GError **error )
{ {
VipsSource *source; VipsGInputStream *gstream = VIPS_G_INPUT_STREAM( stream );
gssize position;
source = VIPS_G_INPUT_STREAM( stream )->source; gssize position;
VIPS_DEBUG_MSG( "vips_g_input_stream_skip: count: %zd\n", count ); VIPS_DEBUG_MSG( "vips_g_input_stream_skip: count: %zd\n", count );
if( g_cancellable_set_error_if_cancelled( cancellable, error ) ) if( g_cancellable_set_error_if_cancelled( cancellable, error ) )
return( -1 ); return( -1 );
position = vips_source_seek( source, count, SEEK_CUR ); position = vips_source_seek( gstream->source, count, SEEK_CUR );
if( position == -1 ) { if( position == -1 ) {
g_set_error( error, G_IO_ERROR, g_set_error( error, G_IO_ERROR,
G_IO_ERROR_FAILED, G_IO_ERROR_FAILED,

View File

@ -742,21 +742,6 @@ vips_shutdown( void )
vips_cache_drop_all(); 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 #if ENABLE_DEPRECATED
im_close_plugins(); im_close_plugins();
#endif #endif
@ -787,6 +772,21 @@ vips_shutdown( void )
VIPS_FREE( vips__argv0 ); VIPS_FREE( vips__argv0 );
VIPS_FREE( vips__prgname ); VIPS_FREE( vips__prgname );
VIPS_FREEF( g_timer_destroy, vips__global_timer ); 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 static gboolean