use anyhow::{Context, Result, ensure};
use resvg::{tiny_skia, usvg};
pub fn load_tree_from_data(data: &[u8]) -> Result<usvg::Tree> {
let opt = usvg::Options::default();
usvg::Tree::from_data(data, &opt).context("parsing SVG")
}
#[allow(clippy::cast_precision_loss)]
pub fn render_size(tree: &usvg::Tree, target_w: u32, target_h: u32) -> Result<tiny_skia::Pixmap> {
let target_w = target_w.max(1);
let target_h = target_h.max(1);
let size = tree.size();
let (w, h) = (size.width(), size.height());
let scale = (target_w as f32 / w).min(target_h as f32 / h);
let scaled_w = w * scale;
let scaled_h = h * scale;
let tx = (target_w as f32 - scaled_w) / 2.0;
let ty = (target_h as f32 - scaled_h) / 2.0;
let transform = tiny_skia::Transform::from_scale(scale, scale).post_translate(tx, ty);
let mut pixmap = tiny_skia::Pixmap::new(target_w, target_h)
.context("allocating pixmap (size too large?)")?;
resvg::render(tree, transform, &mut pixmap.as_mut());
Ok(pixmap)
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
pub fn render_scale(tree: &usvg::Tree, k: f32) -> Result<tiny_skia::Pixmap> {
let size = tree.size();
let w = (size.width() * k).ceil().max(1.0) as u32;
let h = (size.height() * k).ceil().max(1.0) as u32;
let mut pixmap = tiny_skia::Pixmap::new(w, h).context("allocating pixmap")?;
resvg::render(
tree,
tiny_skia::Transform::from_scale(k, k),
&mut pixmap.as_mut(),
);
Ok(pixmap)
}
pub fn encode_png(pixmap: &tiny_skia::Pixmap) -> Result<Vec<u8>> {
pixmap.encode_png().context("encoding PNG")
}
#[allow(clippy::cast_precision_loss)]
pub fn rmse(a: &tiny_skia::Pixmap, b: &tiny_skia::Pixmap) -> Result<f64> {
ensure!(
a.width() == b.width() && a.height() == b.height(),
"comparing a {}x{} render against a {}x{} one",
a.width(),
a.height(),
b.width(),
b.height()
);
let (a, b) = (a.data(), b.data());
let sum: f64 = a
.iter()
.zip(b)
.map(|(x, y)| {
let d = f64::from(*x) - f64::from(*y);
d * d
})
.sum();
Ok((sum / a.len() as f64).sqrt() / 255.0)
}
#[cfg(test)]
mod tests {
use super::*;
fn filled(w: u32, h: u32, color: tiny_skia::Color) -> tiny_skia::Pixmap {
let mut p = tiny_skia::Pixmap::new(w, h).unwrap();
p.fill(color);
p
}
#[allow(clippy::float_cmp)]
#[test]
fn rmse_of_a_pixmap_against_itself_is_zero() {
let p = filled(8, 8, tiny_skia::Color::from_rgba8(12, 34, 56, 255));
assert_eq!(rmse(&p, &p).unwrap(), 0.0);
}
#[allow(clippy::float_cmp)]
#[test]
fn rmse_of_opposite_pixmaps_is_one() {
let white = filled(8, 8, tiny_skia::Color::from_rgba8(255, 255, 255, 255));
let clear = filled(8, 8, tiny_skia::Color::TRANSPARENT);
assert_eq!(rmse(&white, &clear).unwrap(), 1.0);
}
#[test]
fn rmse_rejects_mismatched_sizes() {
let a = filled(8, 8, tiny_skia::Color::TRANSPARENT);
let b = filled(8, 4, tiny_skia::Color::TRANSPARENT);
assert!(rmse(&a, &b).is_err());
}
}