Implement vector pipeline in iced_tiny_skia
This commit is contained in:
parent
bb49e17cab
commit
5b3977daf6
7 changed files with 184 additions and 10 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{Padding, Vector};
|
use crate::{Padding, Vector};
|
||||||
|
|
||||||
/// An amount of space in 2 dimensions.
|
/// An amount of space in 2 dimensions.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct Size<T = f32> {
|
pub struct Size<T = f32> {
|
||||||
/// The width.
|
/// The width.
|
||||||
pub width: T,
|
pub width: T,
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,4 @@ edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced = { path = "../..", features = ["svg"] }
|
iced = { path = "../..", features = ["svg", "tiny-skia", "debug"], default-features = false }
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ edition = "2021"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
image = ["iced_graphics/image"]
|
image = ["iced_graphics/image"]
|
||||||
svg = []
|
svg = ["resvg"]
|
||||||
geometry = ["iced_graphics/geometry"]
|
geometry = ["iced_graphics/geometry"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
@ -34,3 +34,7 @@ default-features = false
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.twox-hash]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.twox-hash]
|
||||||
version = "1.6.1"
|
version = "1.6.1"
|
||||||
features = ["std"]
|
features = ["std"]
|
||||||
|
|
||||||
|
[dependencies.resvg]
|
||||||
|
version = "0.29"
|
||||||
|
optional = true
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,9 @@ pub struct Backend {
|
||||||
|
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
raster_pipeline: crate::raster::Pipeline,
|
raster_pipeline: crate::raster::Pipeline,
|
||||||
|
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
vector_pipeline: crate::vector::Pipeline,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Backend {
|
impl Backend {
|
||||||
|
|
@ -25,6 +28,9 @@ impl Backend {
|
||||||
|
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
raster_pipeline: crate::raster::Pipeline::new(),
|
raster_pipeline: crate::raster::Pipeline::new(),
|
||||||
|
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
vector_pipeline: crate::vector::Pipeline::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,7 +84,10 @@ impl Backend {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.text_pipeline.end_frame();
|
self.text_pipeline.trim_cache();
|
||||||
|
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
self.vector_pipeline.trim_cache();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_primitive(
|
fn draw_primitive(
|
||||||
|
|
@ -181,8 +190,18 @@ impl Backend {
|
||||||
clip_bounds.map(|_| clip_mask as &_),
|
clip_bounds.map(|_| clip_mask as &_),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Primitive::Svg { .. } => {
|
#[cfg(feature = "svg")]
|
||||||
// TODO
|
Primitive::Svg {
|
||||||
|
handle,
|
||||||
|
bounds,
|
||||||
|
color: _, // TODO: Implement color filter
|
||||||
|
} => {
|
||||||
|
self.vector_pipeline.draw(
|
||||||
|
handle,
|
||||||
|
(*bounds + translation) * scale_factor,
|
||||||
|
pixels,
|
||||||
|
clip_bounds.map(|_| clip_mask as &_),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Primitive::Fill {
|
Primitive::Fill {
|
||||||
path,
|
path,
|
||||||
|
|
@ -518,9 +537,8 @@ impl backend::Image for Backend {
|
||||||
impl backend::Svg for Backend {
|
impl backend::Svg for Backend {
|
||||||
fn viewport_dimensions(
|
fn viewport_dimensions(
|
||||||
&self,
|
&self,
|
||||||
_handle: &crate::core::svg::Handle,
|
handle: &crate::core::svg::Handle,
|
||||||
) -> Size<u32> {
|
) -> Size<u32> {
|
||||||
// TODO
|
self.vector_pipeline.viewport_dimensions(handle)
|
||||||
Size::new(0, 0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ mod text;
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
mod raster;
|
mod raster;
|
||||||
|
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
mod vector;
|
||||||
|
|
||||||
#[cfg(feature = "geometry")]
|
#[cfg(feature = "geometry")]
|
||||||
pub mod geometry;
|
pub mod geometry;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ impl Pipeline {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn end_frame(&mut self) {
|
pub fn trim_cache(&mut self) {
|
||||||
self.system
|
self.system
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
|
||||||
149
tiny_skia/src/vector.rs
Normal file
149
tiny_skia/src/vector.rs
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
use crate::core::svg::{Data, Handle};
|
||||||
|
use crate::core::{Rectangle, Size};
|
||||||
|
|
||||||
|
use resvg::usvg;
|
||||||
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::hash_map;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
pub struct Pipeline {
|
||||||
|
cache: RefCell<Cache>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pipeline {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
cache: RefCell::new(Cache::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn viewport_dimensions(&self, handle: &Handle) -> Size<u32> {
|
||||||
|
self.cache
|
||||||
|
.borrow_mut()
|
||||||
|
.viewport_dimensions(handle)
|
||||||
|
.unwrap_or(Size::new(0, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(
|
||||||
|
&mut self,
|
||||||
|
handle: &Handle,
|
||||||
|
bounds: Rectangle,
|
||||||
|
pixels: &mut tiny_skia::PixmapMut<'_>,
|
||||||
|
clip_mask: Option<&tiny_skia::ClipMask>,
|
||||||
|
) {
|
||||||
|
if let Some(image) = self
|
||||||
|
.cache
|
||||||
|
.borrow_mut()
|
||||||
|
.draw(handle, Size::new(bounds.width as u32, bounds.height as u32))
|
||||||
|
{
|
||||||
|
pixels.draw_pixmap(
|
||||||
|
bounds.x as i32,
|
||||||
|
bounds.y as i32,
|
||||||
|
image,
|
||||||
|
&tiny_skia::PixmapPaint::default(),
|
||||||
|
tiny_skia::Transform::identity(),
|
||||||
|
clip_mask,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trim_cache(&mut self) {
|
||||||
|
self.cache.borrow_mut().trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Cache {
|
||||||
|
trees: FxHashMap<u64, Option<resvg::usvg::Tree>>,
|
||||||
|
tree_hits: FxHashSet<u64>,
|
||||||
|
rasters: FxHashMap<(u64, Size<u32>), tiny_skia::Pixmap>,
|
||||||
|
raster_hits: FxHashSet<(u64, Size<u32>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cache {
|
||||||
|
fn load(&mut self, handle: &Handle) -> Option<&usvg::Tree> {
|
||||||
|
let id = handle.id();
|
||||||
|
|
||||||
|
if let hash_map::Entry::Vacant(entry) = self.trees.entry(id) {
|
||||||
|
let svg = match handle.data() {
|
||||||
|
Data::Path(path) => {
|
||||||
|
fs::read_to_string(path).ok().and_then(|contents| {
|
||||||
|
usvg::Tree::from_str(
|
||||||
|
&contents,
|
||||||
|
&usvg::Options::default(),
|
||||||
|
)
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Data::Bytes(bytes) => {
|
||||||
|
usvg::Tree::from_data(bytes, &usvg::Options::default()).ok()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
entry.insert(svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.tree_hits.insert(id);
|
||||||
|
self.trees.get(&id).unwrap().as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn viewport_dimensions(&mut self, handle: &Handle) -> Option<Size<u32>> {
|
||||||
|
let tree = self.load(handle)?;
|
||||||
|
|
||||||
|
Some(Size::new(
|
||||||
|
tree.size.width() as u32,
|
||||||
|
tree.size.height() as u32,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&mut self,
|
||||||
|
handle: &Handle,
|
||||||
|
size: Size<u32>,
|
||||||
|
) -> Option<tiny_skia::PixmapRef<'_>> {
|
||||||
|
if size.width == 0 || size.height == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = handle.id();
|
||||||
|
|
||||||
|
if !self.rasters.contains_key(&(id, size)) {
|
||||||
|
let tree = self.load(handle)?;
|
||||||
|
|
||||||
|
let mut image = tiny_skia::Pixmap::new(size.width, size.height)?;
|
||||||
|
|
||||||
|
resvg::render(
|
||||||
|
tree,
|
||||||
|
if size.width > size.height {
|
||||||
|
usvg::FitTo::Width(size.width)
|
||||||
|
} else {
|
||||||
|
usvg::FitTo::Height(size.height)
|
||||||
|
},
|
||||||
|
tiny_skia::Transform::default(),
|
||||||
|
image.as_mut(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Swap R and B channels for `softbuffer` presentation
|
||||||
|
for pixel in bytemuck::cast_slice_mut::<u8, u32>(image.data_mut()) {
|
||||||
|
*pixel = *pixel & 0xFF00FF00
|
||||||
|
| ((0x000000FF & *pixel) << 16)
|
||||||
|
| ((0x00FF0000 & *pixel) >> 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.rasters.insert((id, size), image);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.raster_hits.insert((id, size));
|
||||||
|
self.rasters.get(&(id, size)).map(tiny_skia::Pixmap::as_ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trim(&mut self) {
|
||||||
|
self.trees.retain(|key, _| self.tree_hits.contains(key));
|
||||||
|
self.rasters.retain(|key, _| self.raster_hits.contains(key));
|
||||||
|
|
||||||
|
self.tree_hits.clear();
|
||||||
|
self.raster_hits.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue