use resvg::{
render,
tiny_skia::{self, Pixmap},
};
use crate::errors::SvgToImageError;
pub fn svg_to_pixmap(svg_data: &str, width: f32, height: f32) -> Result<Pixmap, SvgToImageError> {
let mut opt = resvg::usvg::Options::default();
opt.fontdb_mut()
.load_font_data(include_bytes!("../assets/DejaVuSans.ttf").to_vec());
opt.fontdb_mut().set_sans_serif_family("DejaVu Sans");
opt.default_size = resvg::usvg::Size::from_wh(width, height).expect(
"Provided dimensions should be strictly positive, as Settings struct is validated on \
creation.",
);
let svg_tree = resvg::usvg::Tree::from_data(svg_data.as_bytes(), &opt)?;
let mut pixmap = Pixmap::new(width as u32, height as u32).unwrap();
render(
&svg_tree,
tiny_skia::Transform::identity(),
&mut pixmap.as_mut(),
);
Ok(pixmap)
}
pub fn svg_to_img(
svg_data: &str,
width: f32,
height: f32,
path: impl AsRef<std::path::Path>,
) -> Result<(), SvgToImageError> {
let pixmap = svg_to_pixmap(svg_data, width, height)?;
if let Some(parent) = path.as_ref().parent() {
std::fs::create_dir_all(parent)?;
}
pixmap.save_png(path).map_err(std::io::Error::from)?;
Ok(())
}
#[cfg(test)]
mod tests {
use std::path::Path;
use image::{DynamicImage, GenericImageView, ImageReader};
use resvg::tiny_skia::Pixmap;
use crate::{
graph_to_svg::graph_to_svg_string,
tests::{custom_labels_test_case, position_map_test_case},
};
const MSE_ERROR_TOLERANCE: f64 = 100.0;
fn image_from_pixmap(pixmap: &Pixmap) -> DynamicImage {
let data = pixmap.data();
DynamicImage::ImageRgba8(
image::RgbaImage::from_raw(pixmap.width(), pixmap.height(), data.to_vec()).unwrap(),
)
}
fn mean_squared_error(image_1: &DynamicImage, image_2: &DynamicImage) -> f64 {
let (width_1, height_1) = image_1.dimensions();
let pixels_1 = image_1.to_rgba8();
let pixels_2 = image_2.to_rgba8();
let mut sum = 0.0;
for (pixel_1, pixel_2) in pixels_1.pixels().zip(pixels_2.pixels()) {
for i in 0..4 {
let diff = pixel_1.0[i] as f64 - pixel_2.0[i] as f64;
sum += diff * diff;
}
}
sum / ((width_1 * height_1 * 4) as f64)
}
fn assert_images_equal(generated: &Pixmap, reference_path: &Path) {
let generated = image_from_pixmap(generated);
let reference = ImageReader::open(reference_path)
.expect("Reference path should be valid")
.decode()
.expect("Reference image should be decodable");
assert_eq!(
generated.dimensions(),
reference.dimensions(),
"Image dimensions differ: generated: {:?}, reference: {:?}",
generated.dimensions(),
reference.dimensions()
);
let mse = mean_squared_error(&generated, &reference);
assert!(
mse < MSE_ERROR_TOLERANCE,
"Mean squared error exceeds tolerance of {MSE_ERROR_TOLERANCE}: {mse}",
);
}
#[test]
fn test_svg_to_image_on_custom_labels() {
let (graph, settings) = custom_labels_test_case();
let svg_data = graph_to_svg_string(&graph, &settings);
let pixmap = super::svg_to_pixmap(&svg_data, settings.width, settings.height)
.expect("SVG to pixmap conversion should succeed.");
assert_images_equal(&pixmap, "examples/results/custom_labels.png".as_ref());
}
#[test]
fn test_svg_to_image_on_position_map() {
let (graph, settings) = position_map_test_case();
let svg_data = graph_to_svg_string(&graph, &settings);
let pixmap = super::svg_to_pixmap(&svg_data, settings.width, settings.height)
.expect("SVG to pixmap conversion should succeed.");
assert_images_equal(&pixmap, "examples/results/position_map.png".as_ref());
}
}