mondrian_charts/
chart_to_image.rs1use crate::{chart_to_svg, ChartOptions, MondrianChart};
2use image::{EncodableLayout, RgbaImage};
3use resvg::tiny_skia::{Color, Pixmap};
4use resvg::usvg::{self, TreeTextToPath};
5use std::io::Cursor;
6use thiserror::Error;
7
8pub use image::{ImageError, ImageFormat};
9pub use resvg::usvg::fontdb;
10
11#[derive(Debug, Error)]
12pub enum ImageRenderingError {
13 #[error("Error writing the image format")]
14 ImageError(ImageError),
15 #[error("Invalid color string")]
16 InvalidColor { color: String },
17 #[error("Could not create a buffer for the given image dimensions")]
18 InvalidDimensions,
19 #[error("SVG representation was invalid")]
20 InvalidSvg(String),
21 #[error("{0}")]
22 Other(String),
23}
24
25pub struct ImageOptions {
27 pub fonts: fontdb::Database,
31
32 pub format: ImageFormat,
34
35 pub background_color: String,
39}
40
41pub fn chart_to_image<'a, S, P>(
42 chart: &'a MondrianChart<S, P>,
43 chart_options: &ChartOptions<'a, S>,
44 image_options: &ImageOptions,
45) -> Result<Vec<u8>, ImageRenderingError> {
46 let svg = chart_to_svg(chart, chart_options);
47
48 let image = render_svg_to_buffer(&svg, chart_options, image_options)?;
49
50 let mut buffer = Vec::with_capacity(image.as_bytes().len());
54 image
55 .write_to(&mut Cursor::new(&mut buffer), image_options.format)
56 .map_err(ImageRenderingError::ImageError)?;
57
58 Ok(buffer)
59}
60
61fn render_svg_to_buffer<S>(
62 svg: &str,
63 chart_options: &ChartOptions<S>,
64 image_options: &ImageOptions,
65) -> Result<image::RgbaImage, ImageRenderingError> {
66 let &ChartOptions { width, height, .. } = chart_options;
67
68 let background_color = parse_color(&image_options.background_color)?;
69 let mut pixels =
70 Pixmap::new(width as u32, height as u32).ok_or(ImageRenderingError::InvalidDimensions)?;
71 pixels.fill(background_color);
72
73 let mut tree: usvg::Tree =
74 usvg::TreeParsing::from_data(svg.as_bytes(), &usvg::Options::default())
75 .map_err(|error| ImageRenderingError::InvalidSvg(error.to_string()))?;
76
77 tree.convert_text(&image_options.fonts);
78 resvg::Tree::from_usvg(&tree).render(usvg::Transform::identity(), &mut pixels.as_mut());
79
80 let img = RgbaImage::from_vec(width as u32, height as u32, pixels.take()).ok_or(
81 ImageRenderingError::Other("Could not create ImageBuffer from bytes".to_owned()),
82 )?;
83
84 Ok(img)
85}
86
87fn parse_color(string: &str) -> Result<Color, ImageRenderingError> {
88 csscolorparser::parse(string)
89 .ok()
90 .and_then(|csscolorparser::Color { r, g, b, a }| {
91 Color::from_rgba(r as f32, g as f32, b as f32, a as f32)
92 })
93 .ok_or_else(|| ImageRenderingError::InvalidColor {
94 color: string.to_owned(),
95 })
96}