diff options
Diffstat (limited to 'graphics/src/image/vector.rs')
-rw-r--r-- | graphics/src/image/vector.rs | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/graphics/src/image/vector.rs b/graphics/src/image/vector.rs new file mode 100644 index 00000000..0062e2ce --- /dev/null +++ b/graphics/src/image/vector.rs @@ -0,0 +1,183 @@ +//! Vector image loading and caching + +use iced_native::svg; + +use std::collections::{HashMap, HashSet}; +use std::fs; + +use super::TextureStore; + +/// Entry in cache corresponding to an svg handle +pub enum Svg { + /// Parsed svg + Loaded(usvg::Tree), + /// Svg not found or failed to parse + NotFound, +} + +impl Svg { + /// Viewport width and height + pub fn viewport_dimensions(&self) -> (u32, u32) { + match self { + Svg::Loaded(tree) => { + let size = tree.svg_node().size; + + (size.width() as u32, size.height() as u32) + } + Svg::NotFound => (1, 1), + } + } +} + +/// Caches svg vector and raster data +#[derive(Debug)] +pub struct Cache<T: TextureStore> { + svgs: HashMap<u64, Svg>, + rasterized: HashMap<(u64, u32, u32), T::Entry>, + svg_hits: HashSet<u64>, + rasterized_hits: HashSet<(u64, u32, u32)>, +} + +impl<T: TextureStore> Cache<T> { + /// Load svg + pub fn load(&mut self, handle: &svg::Handle) -> &Svg { + if self.svgs.contains_key(&handle.id()) { + return self.svgs.get(&handle.id()).unwrap(); + } + + let svg = match handle.data() { + svg::Data::Path(path) => { + let tree = fs::read_to_string(path).ok().and_then(|contents| { + usvg::Tree::from_str( + &contents, + &usvg::Options::default().to_ref(), + ) + .ok() + }); + + tree.map(Svg::Loaded).unwrap_or(Svg::NotFound) + } + svg::Data::Bytes(bytes) => { + match usvg::Tree::from_data( + bytes, + &usvg::Options::default().to_ref(), + ) { + Ok(tree) => Svg::Loaded(tree), + Err(_) => Svg::NotFound, + } + } + }; + + let _ = self.svgs.insert(handle.id(), svg); + self.svgs.get(&handle.id()).unwrap() + } + + /// Load svg and upload raster data + pub fn upload( + &mut self, + handle: &svg::Handle, + [width, height]: [f32; 2], + scale: f32, + state: &mut T::State<'_>, + texture_store: &mut T, + ) -> Option<&T::Entry> { + let id = handle.id(); + + let (width, height) = ( + (scale * width).ceil() as u32, + (scale * height).ceil() as u32, + ); + + // TODO: Optimize! + // We currently rerasterize the SVG when its size changes. This is slow + // as heck. A GPU rasterizer like `pathfinder` may perform better. + // It would be cool to be able to smooth resize the `svg` example. + if self.rasterized.contains_key(&(id, width, height)) { + let _ = self.svg_hits.insert(id); + let _ = self.rasterized_hits.insert((id, width, height)); + + return self.rasterized.get(&(id, width, height)); + } + + match self.load(handle) { + Svg::Loaded(tree) => { + if width == 0 || height == 0 { + return None; + } + + // TODO: Optimize! + // We currently rerasterize the SVG when its size changes. This is slow + // as heck. A GPU rasterizer like `pathfinder` may perform better. + // It would be cool to be able to smooth resize the `svg` example. + let mut img = tiny_skia::Pixmap::new(width, height)?; + + resvg::render( + tree, + if width > height { + usvg::FitTo::Width(width) + } else { + usvg::FitTo::Height(height) + }, + img.as_mut(), + )?; + + let mut rgba = img.take(); + rgba.chunks_exact_mut(4).for_each(|rgba| rgba.swap(0, 2)); + + let allocation = texture_store.upload( + width, + height, + bytemuck::cast_slice(rgba.as_slice()), + state, + )?; + log::debug!("allocating {} {}x{}", id, width, height); + + let _ = self.svg_hits.insert(id); + let _ = self.rasterized_hits.insert((id, width, height)); + let _ = self.rasterized.insert((id, width, height), allocation); + + self.rasterized.get(&(id, width, height)) + } + Svg::NotFound => None, + } + } + + /// Load svg and upload raster data + pub fn trim(&mut self, texture_store: &mut T, state: &mut T::State<'_>) { + let svg_hits = &self.svg_hits; + let rasterized_hits = &self.rasterized_hits; + + self.svgs.retain(|k, _| svg_hits.contains(k)); + self.rasterized.retain(|k, entry| { + let retain = rasterized_hits.contains(k); + + if !retain { + texture_store.remove(entry, state); + } + + retain + }); + self.svg_hits.clear(); + self.rasterized_hits.clear(); + } +} + +impl<T: TextureStore> Default for Cache<T> { + fn default() -> Self { + Self { + svgs: HashMap::new(), + rasterized: HashMap::new(), + svg_hits: HashSet::new(), + rasterized_hits: HashSet::new(), + } + } +} + +impl std::fmt::Debug for Svg { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Svg::Loaded(_) => write!(f, "Svg::Loaded"), + Svg::NotFound => write!(f, "Svg::NotFound"), + } + } +} |