use crate::{
AssetSource, DevicePixels, IsZero, RenderImage, Result, SharedString, Size,
swap_rgba_pa_to_bgra,
};
use image::Frame;
use resvg::tiny_skia::Pixmap;
use smallvec::SmallVec;
use std::{
hash::Hash,
sync::{Arc, LazyLock},
};
pub const SMOOTH_SVG_SCALE_FACTOR: f32 = 2.;
#[derive(Clone, PartialEq, Hash, Eq)]
pub(crate) struct RenderSvgParams {
pub(crate) path: SharedString,
pub(crate) size: Size<DevicePixels>,
}
#[derive(Clone)]
pub struct SvgRenderer {
asset_source: Arc<dyn AssetSource>,
usvg_options: Arc<usvg::Options<'static>>,
}
pub enum SvgSize {
Size(Size<DevicePixels>),
ScaleFactor(f32),
}
impl SvgRenderer {
pub fn new(asset_source: Arc<dyn AssetSource>) -> Self {
static FONT_DB: LazyLock<Arc<usvg::fontdb::Database>> = LazyLock::new(|| {
let mut db = usvg::fontdb::Database::new();
db.load_system_fonts();
Arc::new(db)
});
let default_font_resolver = usvg::FontResolver::default_font_selector();
let font_resolver = Box::new(
move |font: &usvg::Font, db: &mut Arc<usvg::fontdb::Database>| {
if db.is_empty() {
*db = FONT_DB.clone();
}
default_font_resolver(font, db)
},
);
let options = usvg::Options {
font_resolver: usvg::FontResolver {
select_font: font_resolver,
select_fallback: usvg::FontResolver::default_fallback_selector(),
},
..Default::default()
};
Self {
asset_source,
usvg_options: Arc::new(options),
}
}
pub fn render_single_frame(
&self,
bytes: &[u8],
scale_factor: f32,
to_brga: bool,
) -> Result<Arc<RenderImage>, usvg::Error> {
self.render_pixmap(
bytes,
SvgSize::ScaleFactor(scale_factor * SMOOTH_SVG_SCALE_FACTOR),
)
.map(|pixmap| {
let mut buffer =
image::ImageBuffer::from_raw(pixmap.width(), pixmap.height(), pixmap.take())
.unwrap();
if to_brga {
for pixel in buffer.chunks_exact_mut(4) {
swap_rgba_pa_to_bgra(pixel);
}
}
let mut image = RenderImage::new(SmallVec::from_const([Frame::new(buffer)]));
image.scale_factor = SMOOTH_SVG_SCALE_FACTOR;
Arc::new(image)
})
}
pub(crate) fn render_alpha_mask(
&self,
params: &RenderSvgParams,
bytes: Option<&[u8]>,
) -> Result<Option<(Size<DevicePixels>, Vec<u8>)>> {
anyhow::ensure!(!params.size.is_zero(), "can't render at a zero size");
let render_pixmap = |bytes| {
let pixmap = self.render_pixmap(bytes, SvgSize::Size(params.size))?;
let size = Size::new(
DevicePixels(pixmap.width() as i32),
DevicePixels(pixmap.height() as i32),
);
let alpha_mask = pixmap
.pixels()
.iter()
.map(|p| p.alpha())
.collect::<Vec<_>>();
Ok(Some((size, alpha_mask)))
};
if let Some(bytes) = bytes {
render_pixmap(bytes)
} else if let Some(bytes) = self.asset_source.load(¶ms.path)? {
render_pixmap(&bytes)
} else {
Ok(None)
}
}
fn render_pixmap(&self, bytes: &[u8], size: SvgSize) -> Result<Pixmap, usvg::Error> {
let tree = usvg::Tree::from_data(bytes, &self.usvg_options)?;
let svg_size = tree.size();
let scale = match size {
SvgSize::Size(size) => size.width.0 as f32 / svg_size.width(),
SvgSize::ScaleFactor(scale) => scale,
};
let mut pixmap = resvg::tiny_skia::Pixmap::new(
(svg_size.width() * scale) as u32,
(svg_size.height() * scale) as u32,
)
.ok_or(usvg::Error::InvalidSize)?;
let transform = resvg::tiny_skia::Transform::from_scale(scale, scale);
resvg::render(&tree, transform, &mut pixmap.as_mut());
Ok(pixmap)
}
}