charts_rs/charts/
encoder.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License at
4//
5//     http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS,
9// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10// See the License for the specific language governing permissions and
11// limitations under the License.
12
13use image::ImageFormat;
14use once_cell::sync::OnceCell;
15use resvg::{tiny_skia, usvg};
16use snafu::{ResultExt, Snafu};
17use std::io::Cursor;
18use std::sync::Arc;
19use usvg::fontdb;
20
21#[derive(Debug, Snafu)]
22pub enum Error {
23    #[snafu(display("Io {file}: {source}"))]
24    Io {
25        file: String,
26        source: std::io::Error,
27    },
28    #[snafu(display("Image size is invalid, width: {width}, height: {height}"))]
29    Size { width: u32, height: u32 },
30    #[snafu(display("Image from raw is fail, size:{size}"))]
31    Raw { size: usize },
32    #[snafu(display("Error to parse: {source}"))]
33    Parse { source: usvg::Error },
34    #[snafu(display("Encode fail: {source}"))]
35    Image { source: image::ImageError },
36}
37pub type Result<T, E = Error> = std::result::Result<T, E>;
38
39pub(crate) fn get_or_init_fontdb(fonts: Option<Vec<&[u8]>>) -> Arc<fontdb::Database> {
40    static GLOBAL_FONT_DB: OnceCell<Arc<fontdb::Database>> = OnceCell::new();
41    GLOBAL_FONT_DB
42        .get_or_init(|| {
43            let mut fontdb = fontdb::Database::new();
44            if let Some(value) = fonts {
45                for item in value.iter() {
46                    fontdb.load_font_data((*item).to_vec());
47                }
48            } else {
49                fontdb.load_system_fonts();
50            }
51            Arc::new(fontdb)
52        })
53        .clone()
54}
55
56fn save_image(svg: &str, format: image::ImageFormat) -> Result<Vec<u8>> {
57    let fontdb = get_or_init_fontdb(None);
58    let tree = usvg::Tree::from_str(
59        svg,
60        &usvg::Options {
61            fontdb,
62            ..Default::default()
63        },
64    )
65    .context(ParseSnafu {})?;
66    let pixmap_size = tree.size().to_int_size();
67    let mut pixmap =
68        tiny_skia::Pixmap::new(pixmap_size.width(), pixmap_size.height()).ok_or(Error::Size {
69            width: pixmap_size.width(),
70            height: pixmap_size.height(),
71        })?;
72    resvg::render(&tree, tiny_skia::Transform::default(), &mut pixmap.as_mut());
73
74    let data = pixmap.data().to_vec();
75    let size = data.len();
76    let rgba_image = image::RgbaImage::from_raw(pixmap.width(), pixmap.height(), data)
77        .ok_or(Error::Raw { size })?;
78    let mut buf = Cursor::new(vec![]);
79
80    if format == ImageFormat::Jpeg {
81        image::DynamicImage::ImageRgba8(rgba_image)
82            .to_rgb8()
83            .write_to(&mut buf, format)
84            .context(ImageSnafu)?;
85    } else {
86        rgba_image.write_to(&mut buf, format).context(ImageSnafu)?;
87    }
88    Ok(buf.into_inner())
89}
90
91/// Converts svg to png.
92pub fn svg_to_png(svg: &str) -> Result<Vec<u8>> {
93    save_image(svg, image::ImageFormat::Png)
94}
95
96/// Converts svg to jpeg, the quality is 80.
97pub fn svg_to_jpeg(svg: &str) -> Result<Vec<u8>> {
98    save_image(svg, image::ImageFormat::Jpeg)
99}
100
101/// Converts svg to webp.
102pub fn svg_to_webp(svg: &str) -> Result<Vec<u8>> {
103    save_image(svg, image::ImageFormat::WebP)
104}
105
106/// Converts svg to avif.
107pub fn svg_to_avif(svg: &str) -> Result<Vec<u8>> {
108    save_image(svg, image::ImageFormat::Avif)
109}