resvgはRustで書かれたSVG描画ライブラリです。公式ドキュメントによると、規格への遵守度がlibrsvgはおろかChromeやFirefoxを抑えて1位とのことです。ただし、アニメーションやスクリプトなどには対応していません。
この記事では、resvgを使ってSVG文書をラスター化する方法を紹介します。ラスター化の基本的な流れは次の通りです。
- SVG文書データから
usvg::Treeオブジェクトを作成する -
tiny_skia::PixmapMutオブジェクトを作成する -
resvg::renderを使って描画処理を実行する - 描画結果を利用する
Tree
TreeのコンストラクタはSVG文書データとusvg::Optionsオブジェクトを受け取ります。SVG文書データは&[u8]、&str、roxmltree::Documentのいずれかです。OptionsオブジェクトはSVG文書を処理する方法をカスタマイズします。
Optionsの大体のフィールドの意味はドキュメントを読めばわかると思いますが、dpiについては注意が必要です。このフィールドは物理DPIではなく論理DPIを指定します。論理DPIはSVG生成ソフトが意図した値に設定しなければなりません。SVG 2の場合は必ず96.0です。
// SVG文書には "# が頻出することに注意(fill="#eee"など)
let svg_str = r##"<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
<defs>
<clipPath id="clip-1">
<rect x="0" y="0" width="60" height="60"/>
<rect x="40" y="40" width="60" height="60"/>
</clipPath>
<linearGradient id="grad-1" y2="1">
<stop offset="0" stop-color="red"/>
<stop offset="1" stop-color="yellow"/>
</linearGradient>
</defs>
<circle cx="50" cy="50" r="50" fill="url(#grad-1)" clip-path="url(#clip-1)"/>
</svg>
"##;
let tree = Tree::from_str(svg_str, &Options::default()).unwrap();
PixmapMut
PixmapRef、PixmapMut、Pixmapはいずれもバッファのラッパーです。これらのオブジェクトはas_ref、as_mut、to_ownedによって相互変換することができます。
PixmapRefはバッファを不変借用し、バッファの情報を利用するメソッドを提供します。
PixmapMutはバッファを可変借用し、バッファに対して描画操作を実施するメソッドを提供します。
Pixmapはバッファを所有します。PixmapMutとは異なり、アロケーションやムーブを伴うコンストラクタが提供されます。
どの型を使用する場合でもバッファの横幅と縦幅を整数で指定する必要があります。この値はSVG文書の横幅と縦幅に物理ピクセル比を乗じた値(を丸めた値)です。物理ピクセル比は物理DPI÷論理DPIで求められます。
let physical_dpi = 120.0;
let physical_pixel_ratio = physical_dpi / 96.0;
let tree_size = tree.size();
let pixmap_width = (tree_size.width() * physical_pixel_ratio).round() as u32;
let pixmap_height = (tree_size.height() * physical_pixel_ratio).round() as u32;
let mut pixmap = Pixmap::new(pixmap_width, pixmap_height).unwrap();
render
renderはTreeとPixmapMutのほかにSVG文書全体に適用されるusvg::Transformオブジェクトを受け取ります。これを使って論理ピクセル座標を物理ピクセル座標に変換します。
let transform = Transform::from_scale(physical_pixel_ratio, physical_pixel_ratio);
resvg::render(&tree, transform, &mut pixmap.as_mut());
これでSVG文書の内容がPixmapに書き込まれました。あとはバッファの内容を好きに利用するだけです。今回はPNGとして保存します。
pixmap.save_png("image.png").unwrap();
結果
以上のプログラムによって作成された画像とInkscapeによって作成された画像を並べます。
見た目は全く変わりませんが、ファイルサイズが倍以上違います。tiny-skiaでは圧縮時間を優先するのか、圧縮率を優先するかなどの設定ができない上、解像度などのメタデータを含めることができないので、真面目なコードでは別のエンコーダを使用したほうがいいのかもしれません。

