Skip to content

Commit

Permalink
Add SVG load support via resvg (#2)
Browse files Browse the repository at this point in the history
Co-authored-by: Kleis Auke Wolthuizen <github@kleisauke.nl>
  • Loading branch information
RReverser and kleisauke authored Jan 24, 2023
1 parent cbc753c commit 95c6dff
Show file tree
Hide file tree
Showing 7 changed files with 263 additions and 23 deletions.
6 changes: 4 additions & 2 deletions libvips/foreign/foreign.c
Original file line number Diff line number Diff line change
Expand Up @@ -3006,11 +3006,13 @@ vips_foreign_operation_init( void )
vips_foreign_load_pdf_source_get_type();
#endif /*HAVE_PDFIUM*/

#ifdef HAVE_RSVG
#if (defined(HAVE_RSVG) || defined(HAVE_RESVG)) && !defined(RESVG_MODULE)
vips_foreign_load_svg_file_get_type();
vips_foreign_load_svg_buffer_get_type();
#ifdef HAVE_RSVG
vips_foreign_load_svg_source_get_type();
#endif /*HAVE_RSVG*/
#endif
#endif /*defined(HAVE_RSVG) || defined(HAVE_RESVG)*/

#if defined(HAVE_LIBJXL) && !defined(LIBJXL_MODULE)
vips_foreign_load_jxl_file_get_type();
Expand Down
9 changes: 8 additions & 1 deletion libvips/foreign/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ foreign_sources = files(
'rawsave.c',
'spngload.c',
'spngsave.c',
'svgload.c',
'tiff2vips.c',
'tiff.c',
'tiffload.c',
Expand Down Expand Up @@ -123,6 +122,14 @@ endif

libvips_sources += foreign_sources

resvg_module_sources = files(
'svgload.c',
)

if not resvg_module
foreign_sources += resvg_module_sources
endif

foreign_lib = static_library('foreign',
foreign_sources,
foreign_headers,
Expand Down
158 changes: 138 additions & 20 deletions libvips/foreign/svgload.c
Original file line number Diff line number Diff line change
Expand Up @@ -81,25 +81,38 @@
#include <cairo.h>
#include <librsvg/rsvg.h>

/* Render SVGs with tiles this size. They need to be pretty big to limit
* overcomputation.
/* A handy #define for we-will-handle-svgz.
*/
#define TILE_SIZE (2000)
#if LIBRSVG_CHECK_FEATURE(SVGZ)
#define HANDLE_SVGZ
#endif

/* The <svg tag must appear within this many bytes of the start of the file.
*/
#define SVG_HEADER_SIZE (1000)
#elif defined(HAVE_RESVG)

/* A handy #define for we-will-handle-svgz.
*/
#if LIBRSVG_CHECK_FEATURE(SVGZ) && defined(HAVE_ZLIB)
#include <resvg.h>
#define HANDLE_SVGZ

#endif

#if defined(HAVE_RSVG) || defined(HAVE_RESVG)

#ifndef HAVE_ZLIB
#undef HANDLE_SVGZ
#endif

#ifdef HANDLE_SVGZ
#include <zlib.h>
#endif

/* Render SVGs with tiles this size. They need to be pretty big to limit
* overcomputation.
*/
#define TILE_SIZE (2000)

/* The <svg tag must appear within this many bytes of the start of the file.
*/
#define SVG_HEADER_SIZE (1000)

typedef struct _VipsForeignLoadSvg {
VipsForeignLoad parent_object;

Expand All @@ -119,7 +132,12 @@ typedef struct _VipsForeignLoadSvg {
*/
gboolean unlimited;

#ifdef HAVE_RSVG
RsvgHandle *page;
#else
resvg_options *options;
resvg_render_tree *tree;
#endif

} VipsForeignLoadSvg;

Expand Down Expand Up @@ -308,7 +326,12 @@ vips_foreign_load_svg_dispose( GObject *gobject )
{
VipsForeignLoadSvg *svg = (VipsForeignLoadSvg *) gobject;

#ifdef HAVE_RSVG
VIPS_UNREF( svg->page );
#else
resvg_options_destroy( svg->options );
VIPS_FREEF( resvg_tree_destroy, svg->tree );
#endif

G_OBJECT_CLASS( vips_foreign_load_svg_parent_class )->
dispose( gobject );
Expand All @@ -328,6 +351,7 @@ vips_foreign_load_svg_get_flags( VipsForeignLoad *load )
return( VIPS_FOREIGN_PARTIAL );
}

#ifdef HAVE_RSVG
#if LIBRSVG_CHECK_VERSION( 2, 52, 0 )
/* Derived from `CssLength::to_user` in librsvg.
* https://gitlab.gnome.org/GNOME/librsvg/-/blob/e6607c9ae8d8409d4efff6b12993717400b3356e/src/length.rs#L368
Expand Down Expand Up @@ -386,6 +410,7 @@ svg_css_length_to_pixels( RsvgLength length, double dpi )
return value;
}
#endif
#endif

static int
vips_foreign_load_svg_get_natural_size( VipsForeignLoadSvg *svg,
Expand All @@ -396,6 +421,7 @@ vips_foreign_load_svg_get_natural_size( VipsForeignLoadSvg *svg,
double width;
double height;

#ifdef HAVE_RSVG
#if LIBRSVG_CHECK_VERSION( 2, 52, 0 )

if( !rsvg_handle_get_intrinsic_size_in_pixels( svg->page,
Expand Down Expand Up @@ -483,6 +509,11 @@ vips_foreign_load_svg_get_natural_size( VipsForeignLoadSvg *svg,
}

#endif /*LIBRSVG_CHECK_VERSION( 2, 52, 0 )*/
#else /* HAVE_RSVG */
resvg_size size = resvg_get_image_size( svg->tree );
width = size.width;
height = size.height;
#endif

/* width or height below 0.5 can't be rounded to 1.
*/
Expand All @@ -505,9 +536,11 @@ vips_foreign_load_svg_get_scaled_size( VipsForeignLoadSvg *svg,
double width;
double height;

#ifdef HAVE_RSVG
/* Get dimensions with the default dpi.
*/
rsvg_handle_set_dpi( svg->page, 72.0 );
#endif
if( vips_foreign_load_svg_get_natural_size( svg, &width, &height ) )
return( -1 );

Expand Down Expand Up @@ -565,23 +598,25 @@ vips_foreign_load_svg_generate( VipsRegion *or,
void *seq, void *a, void *b, gboolean *stop )
{
const VipsForeignLoadSvg *svg = (VipsForeignLoadSvg *) a;
const VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( svg );
const VipsRect *r = &or->valid;

cairo_surface_t *surface;
cairo_t *cr;
int y;

#ifdef DEBUG
printf( "vips_foreign_load_svg_generate:\n "
"left = %d, top = %d, width = %d, height = %d\n",
r->left, r->top, r->width, r->height );
#endif /*DEBUG*/

/* rsvg won't always paint the background.
/* SVG won't always paint the background.
*/
vips_region_black( or );

#ifdef HAVE_RSVG
const VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( svg );

cairo_surface_t *surface;
cairo_t *cr;
int y;

surface = cairo_image_surface_create_for_data(
VIPS_REGION_ADDR( or, r->left, r->top ),
CAIRO_FORMAT_ARGB32,
Expand Down Expand Up @@ -644,6 +679,42 @@ vips_foreign_load_svg_generate( VipsRegion *or,
vips__premultiplied_bgra2rgba(
(guint32 *) VIPS_REGION_ADDR( or, r->left, r->top + y ),
r->width );
#else

VipsPel *q = VIPS_REGION_ADDR( or, r->left, r->top );

resvg_render(
svg->tree,
(resvg_fit_to) { .type = RESVG_FIT_TO_TYPE_ORIGINAL },
(resvg_transform) {
.a = svg->cairo_scale,
.d = svg->cairo_scale,
.e = -r->left,
.f = -r->top,
},
r->width,
r->height,
(char *) q
);

/* Just unpremultiply.
*/
for( int i = 0; i < r->width * r->height; i++ ) {
VipsPel * restrict p = &q[i * 4];
VipsPel x = p[3];

/* Skip transparent and fully opaque pixels.
*/
if( x == 0 || x == 255 )
continue;

/* Any compiler will unroll it.
*/
for( int j = 0; j < 3; j++ )
p[j] = 255 * p[j] / x;
}

#endif

return( 0 );
}
Expand Down Expand Up @@ -731,8 +802,16 @@ vips_foreign_load_svg_init( VipsForeignLoadSvg *svg )
svg->dpi = 72.0;
svg->scale = 1.0;
svg->cairo_scale = 1.0;
#ifdef HAVE_RESVG
svg->options = resvg_options_create();

/* Get dimensions with the default dpi.
*/
resvg_options_set_dpi( svg->options, 72.0 );
#endif
}

#ifdef HAVE_RSVG
typedef struct _VipsForeignLoadSvgSource {
VipsForeignLoadSvg parent_object;

Expand Down Expand Up @@ -783,7 +862,6 @@ vips_foreign_load_svg_source_header( VipsForeignLoad *load )
return( -1 );
}
g_object_unref( gstream );

return( vips_foreign_load_svg_header( load ) );
}

Expand Down Expand Up @@ -833,6 +911,7 @@ static void
vips_foreign_load_svg_source_init( VipsForeignLoadSvgSource *source )
{
}
#endif

typedef struct _VipsForeignLoadSvgFile {
VipsForeignLoadSvg parent_object;
Expand All @@ -859,11 +938,34 @@ vips_foreign_load_svg_file_is_a( const char *filename )
vips_foreign_load_svg_is_a( buf, bytes ) );
}

#ifdef HAVE_RESVG
static const char *
resvg_error_msg( resvg_error e ) {
switch( e ) {
case RESVG_ERROR_NOT_AN_UTF8_STR:
return "only UTF-8 content is supported";
case RESVG_ERROR_FILE_OPEN_FAILED:
return "failed to open the provided file";
case RESVG_ERROR_MALFORMED_GZIP:
return "compressed SVG must use the GZip algorithm";
case RESVG_ERROR_ELEMENTS_LIMIT_REACHED:
return "we do not allow SVG with more than 1_000_000 elements for security reasons";
case RESVG_ERROR_INVALID_SIZE:
return "SVG doesn't have a valid size";
case RESVG_ERROR_PARSING_FAILED:
return "failed to parse SVG data";
default:
return "unknown error";
}
}
#endif

static int
vips_foreign_load_svg_file_header( VipsForeignLoad *load )
{
VipsForeignLoadSvg *svg = (VipsForeignLoadSvg *) load;
VipsForeignLoadSvgFile *file = (VipsForeignLoadSvgFile *) load;
#ifdef HAVE_RSVG
RsvgHandleFlags flags = svg->unlimited ? RSVG_HANDLE_FLAG_UNLIMITED : 0;

GError *error = NULL;
Expand All @@ -878,7 +980,15 @@ vips_foreign_load_svg_file_header( VipsForeignLoad *load )
return( -1 );
}
g_object_unref( gfile );

#else
resvg_error error = resvg_parse_tree_from_file(
file->filename, svg->options, &svg->tree );
if( error != RESVG_OK ) {
vips_error( VIPS_OBJECT_GET_CLASS( svg )->nickname,
"%s", resvg_error_msg( error ) );
return( -1 );
}
#endif
VIPS_SETSTR( load->out->filename, file->filename );

return( vips_foreign_load_svg_header( load ) );
Expand All @@ -888,7 +998,7 @@ static const char *vips_foreign_svg_suffs[] = {
".svg",
/* librsvg supports svgz directly, no need to check for zlib here.
*/
#if LIBRSVG_CHECK_FEATURE(SVGZ)
#ifdef HANDLE_SVGZ
".svgz",
".svg.gz",
#endif
Expand Down Expand Up @@ -948,6 +1058,7 @@ vips_foreign_load_svg_buffer_header( VipsForeignLoad *load )
VipsForeignLoadSvg *svg = (VipsForeignLoadSvg *) load;
VipsForeignLoadSvgBuffer *buffer =
(VipsForeignLoadSvgBuffer *) load;
#ifdef HAVE_RSVG
RsvgHandleFlags flags = svg->unlimited ? RSVG_HANDLE_FLAG_UNLIMITED : 0;

GError *error = NULL;
Expand All @@ -963,7 +1074,15 @@ vips_foreign_load_svg_buffer_header( VipsForeignLoad *load )
return( -1 );
}
g_object_unref( gstream );

#else
resvg_error error = resvg_parse_tree_from_data(
buffer->buf->data, buffer->buf->length, svg->options, &svg->tree );
if( error != RESVG_OK ) {
vips_error( VIPS_OBJECT_GET_CLASS( svg )->nickname,
"%s", resvg_error_msg( error ) );
return( -1 );
}
#endif
return( vips_foreign_load_svg_header( load ) );
}

Expand Down Expand Up @@ -1146,4 +1265,3 @@ vips_svgload_source( VipsSource *source, VipsImage **out, ... )

return( result );
}

15 changes: 15 additions & 0 deletions libvips/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,18 @@ if openslide_module
install_dir: module_dir
)
endif

if resvg_module
shared_module('vips-resvg',
'module/resvg.c',
resvg_module_sources,
c_args: module_c_args,
link_args: module_link_args,
name_prefix: '',
name_suffix: module_suffix,
dependencies: [module_dep, resvg_dep],
gnu_symbol_visibility: 'hidden',
install: true,
install_dir: module_dir
)
endif
Loading

0 comments on commit 95c6dff

Please sign in to comment.