From 5b3977daf6df624ca5d5e1a21ce282161234b22d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 Mar 2023 06:09:51 +0100 Subject: Implement `vector` pipeline in `iced_tiny_skia` --- tiny_skia/src/vector.rs | 149 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 tiny_skia/src/vector.rs (limited to 'tiny_skia/src/vector.rs') diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs new file mode 100644 index 00000000..fd9cfdc5 --- /dev/null +++ b/tiny_skia/src/vector.rs @@ -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, +} + +impl Pipeline { + pub fn new() -> Self { + Self { + cache: RefCell::new(Cache::default()), + } + } + + pub fn viewport_dimensions(&self, handle: &Handle) -> Size { + 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>, + tree_hits: FxHashSet, + rasters: FxHashMap<(u64, Size), tiny_skia::Pixmap>, + raster_hits: FxHashSet<(u64, Size)>, +} + +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> { + 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, + ) -> Option> { + 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::(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(); + } +} -- cgit From 24c3d20a76e45feddac67ed62249796c774ce330 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 Mar 2023 06:34:27 +0100 Subject: Tell `clippy` to go learn the borrow rules --- tiny_skia/src/vector.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'tiny_skia/src/vector.rs') diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs index fd9cfdc5..89063c4c 100644 --- a/tiny_skia/src/vector.rs +++ b/tiny_skia/src/vector.rs @@ -109,6 +109,7 @@ impl Cache { let id = handle.id(); + #[allow(clippy::map_entry)] if !self.rasters.contains_key(&(id, size)) { let tree = self.load(handle)?; -- cgit From 424ac8177309440bbd8efe0dd9f7622cb10807ce Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 9 Mar 2023 04:48:35 +0100 Subject: Implement color filter support for `Primitive::Svg` in `iced_tiny_skia` --- tiny_skia/src/vector.rs | 66 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 18 deletions(-) (limited to 'tiny_skia/src/vector.rs') diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs index 89063c4c..8509b761 100644 --- a/tiny_skia/src/vector.rs +++ b/tiny_skia/src/vector.rs @@ -1,5 +1,5 @@ use crate::core::svg::{Data, Handle}; -use crate::core::{Rectangle, Size}; +use crate::core::{Color, Rectangle, Size}; use resvg::usvg; use rustc_hash::{FxHashMap, FxHashSet}; @@ -29,15 +29,16 @@ impl Pipeline { pub fn draw( &mut self, handle: &Handle, + color: Option, 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)) - { + if let Some(image) = self.cache.borrow_mut().draw( + handle, + color, + Size::new(bounds.width as u32, bounds.height as u32), + ) { pixels.draw_pixmap( bounds.x as i32, bounds.y as i32, @@ -58,8 +59,15 @@ impl Pipeline { struct Cache { trees: FxHashMap>, tree_hits: FxHashSet, - rasters: FxHashMap<(u64, Size), tiny_skia::Pixmap>, - raster_hits: FxHashSet<(u64, Size)>, + rasters: FxHashMap, + raster_hits: FxHashSet, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +struct RasterKey { + id: u64, + color: Option<[u8; 4]>, + size: Size, } impl Cache { @@ -101,16 +109,21 @@ impl Cache { fn draw( &mut self, handle: &Handle, + color: Option, size: Size, ) -> Option> { if size.width == 0 || size.height == 0 { return None; } - let id = handle.id(); + let key = RasterKey { + id: handle.id(), + color: color.map(Color::into_rgba8), + size, + }; #[allow(clippy::map_entry)] - if !self.rasters.contains_key(&(id, size)) { + if !self.rasters.contains_key(&key) { let tree = self.load(handle)?; let mut image = tiny_skia::Pixmap::new(size.width, size.height)?; @@ -126,18 +139,35 @@ impl Cache { image.as_mut(), )?; - // Swap R and B channels for `softbuffer` presentation - for pixel in bytemuck::cast_slice_mut::(image.data_mut()) { - *pixel = *pixel & 0xFF00FF00 - | ((0x000000FF & *pixel) << 16) - | ((0x00FF0000 & *pixel) >> 16); + if let Some([r, g, b, a]) = key.color { + // TODO: Blend alpha + let color = tiny_skia::ColorU8::from_rgba(b, g, r, a) + .premultiply() + .get() + & 0x00FFFFFF; + + // Apply color filter + for pixel in + bytemuck::cast_slice_mut::(image.data_mut()) + { + *pixel = *pixel & 0xFF000000 | color; + } + } else { + // Swap R and B channels for `softbuffer` presentation + for pixel in + bytemuck::cast_slice_mut::(image.data_mut()) + { + *pixel = *pixel & 0xFF00FF00 + | ((0x000000FF & *pixel) << 16) + | ((0x00FF0000 & *pixel) >> 16); + } } - self.rasters.insert((id, size), image); + self.rasters.insert(key, image); } - self.raster_hits.insert((id, size)); - self.rasters.get(&(id, size)).map(tiny_skia::Pixmap::as_ref) + self.raster_hits.insert(key); + self.rasters.get(&key).map(tiny_skia::Pixmap::as_ref) } fn trim(&mut self) { -- cgit From 9c63eb7df559e58b14188b4096e9bd206444bbf3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Apr 2023 16:46:27 +0200 Subject: Update `tiny-skia` and `resvg` --- tiny_skia/src/vector.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'tiny_skia/src/vector.rs') diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs index 8509b761..fc411fdd 100644 --- a/tiny_skia/src/vector.rs +++ b/tiny_skia/src/vector.rs @@ -32,7 +32,7 @@ impl Pipeline { color: Option, bounds: Rectangle, pixels: &mut tiny_skia::PixmapMut<'_>, - clip_mask: Option<&tiny_skia::ClipMask>, + clip_mask: Option<&tiny_skia::Mask>, ) { if let Some(image) = self.cache.borrow_mut().draw( handle, @@ -72,6 +72,8 @@ struct RasterKey { impl Cache { fn load(&mut self, handle: &Handle) -> Option<&usvg::Tree> { + use usvg::TreeParsing; + let id = handle.id(); if let hash_map::Entry::Vacant(entry) = self.trees.entry(id) { @@ -131,9 +133,9 @@ impl Cache { resvg::render( tree, if size.width > size.height { - usvg::FitTo::Width(size.width) + resvg::FitTo::Width(size.width) } else { - usvg::FitTo::Height(size.height) + resvg::FitTo::Height(size.height) }, tiny_skia::Transform::default(), image.as_mut(), -- cgit From 3f1c8a8d288d823529e81124bd514f626e84c610 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 9 May 2023 14:57:50 -0400 Subject: fix: tiny-skia svg premultiply final filtered color --- tiny_skia/src/vector.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'tiny_skia/src/vector.rs') diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs index fc411fdd..a3f3c2e3 100644 --- a/tiny_skia/src/vector.rs +++ b/tiny_skia/src/vector.rs @@ -141,18 +141,19 @@ impl Cache { image.as_mut(), )?; - if let Some([r, g, b, a]) = key.color { - // TODO: Blend alpha - let color = tiny_skia::ColorU8::from_rgba(b, g, r, a) - .premultiply() - .get() - & 0x00FFFFFF; - + if let Some([r, g, b, _]) = key.color { // Apply color filter for pixel in bytemuck::cast_slice_mut::(image.data_mut()) { - *pixel = *pixel & 0xFF000000 | color; + *pixel = tiny_skia::ColorU8::from_rgba( + b, + g, + r, + (*pixel >> 24) as u8, + ) + .premultiply() + .get(); } } else { // Swap R and B channels for `softbuffer` presentation -- cgit