All formats

resvg

LanguageRust (C API and CLI available)
LicenseApache-2.0 OR MIT
Repositorygithub.com/linebender/resvg
Documentationdocs.rs/resvg, docs.rs/usvg
Depends onpure Rust: usvg (parser), tiny-skia (rasterizer), fontdb and rustybuzz (text)
Read / writeread (renders to RGBA pixmaps, PNG) / write normalized SVG via usvg

resvg renders static SVG: shapes, paths, text with full shaping, gradients, patterns, clipping, masks, markers, filters and CSS. It passes more of its 1600-test suite than any other library (support table). It doesn’t support animation or scripts, by design.

Rendering happens in 2 steps. usvg parses SVG and resolves everything (CSS, <use>, units, text layout) into a small tree with only groups, paths, images and text. resvg draws that tree with tiny-skia. There’s no C code in the build, it compiles to WebAssembly, and output is pixel-identical across platforms. Typst, many static site and diagram tools, and image servers use it.

Installation#

Shell
cargo add resvg

resvg re-exports usvg and tiny_skia, so one dependency is enough. The command line tool installs with cargo install resvg.

Basic usage: convert SVG to PNG#

Rust
use resvg::{tiny_skia, usvg};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let data = std::fs::read("in.svg")?;

    let mut opt = usvg::Options::default();
    opt.fontdb_mut().load_system_fonts(); // needed if the SVG has text
    let tree = usvg::Tree::from_data(&data, &opt)?;

    let size = tree.size().to_int_size();
    let mut pixmap = tiny_skia::Pixmap::new(size.width(), size.height()).ok_or("invalid size")?;
    resvg::render(&tree, tiny_skia::Transform::default(), &mut pixmap.as_mut());
    pixmap.save_png("out.png")?;
    Ok(())
}

Tree::from_data also reads .svgz (gzip) data.

How to render SVG at a given width#

Scale with a transform and size the pixmap to match:

Rust
let scale = 512.0 / tree.size().width();
let w = (tree.size().width() * scale).ceil() as u32;
let h = (tree.size().height() * scale).ceil() as u32;

let mut pixmap = tiny_skia::Pixmap::new(w, h).ok_or("invalid size")?;
pixmap.fill(tiny_skia::Color::WHITE); // optional background
resvg::render(&tree, tiny_skia::Transform::from_scale(scale, scale), &mut pixmap.as_mut());
pixmap.save_png("out-512.png")?;

How to get the raw pixels#

Rust
let rgba: &[u8] = pixmap.data(); // premultiplied RGBA, width * height * 4 bytes

Use pixmap.encode_png() to get PNG bytes in memory instead of writing a file.

How to read shapes and bounding boxes#

usvg gives you the resolved geometry, with transforms and styles applied:

Rust
use resvg::usvg;

for node in tree.root().children() {
    match node {
        usvg::Node::Group(g) => println!("group {}", g.id()),
        usvg::Node::Path(p) => println!("path {:?} bbox {:?}", p.id(), p.bounding_box()),
        usvg::Node::Image(_) => println!("image"),
        usvg::Node::Text(_) => println!("text"),
    }
}
println!("drawing bounds: {:?}", tree.root().abs_bounding_box());

How to write a simplified SVG#

usvg can serialize its tree back to SVG: the result has no CSS, no <use> and text converted to paths, which other tools read more reliably:

Rust
let simplified: String = tree.to_string(&usvg::WriteOptions::default());
std::fs::write("simplified.svg", simplified)?;

How to convert from the command line#

Shell
resvg in.svg out.png
resvg -w 1024 in.svg out.png              # width 1024, height keeps the ratio
resvg --background white in.svg out.png

Tips#