From 8c373cd497e370d356b480380482779397bdb510 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 25 Feb 2023 15:38:25 +0100 Subject: Scaffold `iced_tiny_skia` and connect it to `iced_renderer` --- tiny_skia/src/backend.rs | 87 ++++++++++++++++++++++++++++++++++ tiny_skia/src/lib.rs | 16 +++++++ tiny_skia/src/settings.rs | 24 ++++++++++ tiny_skia/src/window.rs | 3 ++ tiny_skia/src/window/compositor.rs | 96 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 226 insertions(+) create mode 100644 tiny_skia/src/backend.rs create mode 100644 tiny_skia/src/lib.rs create mode 100644 tiny_skia/src/settings.rs create mode 100644 tiny_skia/src/window.rs create mode 100644 tiny_skia/src/window/compositor.rs (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs new file mode 100644 index 00000000..4282a745 --- /dev/null +++ b/tiny_skia/src/backend.rs @@ -0,0 +1,87 @@ +use crate::{Font, Settings, Size}; + +use iced_graphics::backend; +use iced_graphics::text; + +use std::borrow::Cow; + +pub struct Backend { + default_font: Font, + default_text_size: f32, +} + +impl Backend { + pub fn new(settings: Settings) -> Self { + Self { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + } + } +} + +impl iced_graphics::Backend for Backend { + fn trim_measurements(&mut self) { + // TODO + } +} + +impl backend::Text for Backend { + const ICON_FONT: Font = Font::Name("Iced-Icons"); + const CHECKMARK_ICON: char = '\u{f00c}'; + const ARROW_DOWN_ICON: char = '\u{e800}'; + + fn default_font(&self) -> Font { + self.default_font + } + + fn default_size(&self) -> f32 { + self.default_text_size + } + + fn measure( + &self, + _contents: &str, + _size: f32, + _font: Font, + _bounds: Size, + ) -> (f32, f32) { + // TODO + (0.0, 0.0) + } + + fn hit_test( + &self, + _contents: &str, + _size: f32, + _font: Font, + _bounds: Size, + _point: iced_native::Point, + _nearest_only: bool, + ) -> Option { + // TODO + None + } + + fn load_font(&mut self, _font: Cow<'static, [u8]>) { + // TODO + } +} + +#[cfg(feature = "image")] +impl backend::Image for Backend { + fn dimensions(&self, _handle: &iced_native::image::Handle) -> Size { + // TODO + Size::new(0, 0) + } +} + +#[cfg(feature = "svg")] +impl backend::Svg for Backend { + fn viewport_dimensions( + &self, + _handle: &iced_native::svg::Handle, + ) -> Size { + // TODO + Size::new(0, 0) + } +} diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs new file mode 100644 index 00000000..fce44e9e --- /dev/null +++ b/tiny_skia/src/lib.rs @@ -0,0 +1,16 @@ +pub mod window; + +mod backend; +mod settings; + +pub use backend::Backend; +pub use settings::Settings; + +pub use iced_graphics::{Color, Error, Font, Point, Size, Vector, Viewport}; + +/// A [`tiny-skia`] graphics renderer for [`iced`]. +/// +/// [`tiny-skia`]: https://github.com/RazrFalcon/tiny-skia +/// [`iced`]: https://github.com/iced-rs/iced +pub type Renderer = + iced_graphics::Renderer; diff --git a/tiny_skia/src/settings.rs b/tiny_skia/src/settings.rs new file mode 100644 index 00000000..88098345 --- /dev/null +++ b/tiny_skia/src/settings.rs @@ -0,0 +1,24 @@ +use crate::Font; + +/// The settings of a [`Backend`]. +/// +/// [`Backend`]: crate::Backend +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Settings { + /// The default [`Font`] to use. + pub default_font: Font, + + /// The default size of text. + /// + /// By default, it will be set to `16.0`. + pub default_text_size: f32, +} + +impl Default for Settings { + fn default() -> Settings { + Settings { + default_font: Font::SansSerif, + default_text_size: 16.0, + } + } +} diff --git a/tiny_skia/src/window.rs b/tiny_skia/src/window.rs new file mode 100644 index 00000000..d8d9378e --- /dev/null +++ b/tiny_skia/src/window.rs @@ -0,0 +1,3 @@ +pub mod compositor; + +pub use compositor::{Compositor, Surface}; diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs new file mode 100644 index 00000000..053bb29b --- /dev/null +++ b/tiny_skia/src/window/compositor.rs @@ -0,0 +1,96 @@ +use crate::{Backend, Color, Error, Renderer, Settings, Viewport}; + +use iced_graphics::window::compositor::{self, Information, SurfaceError}; +use iced_graphics::Primitive; + +use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; +use std::marker::PhantomData; + +pub struct Compositor { + _theme: PhantomData, +} + +pub struct Surface; + +impl iced_graphics::window::Compositor for Compositor { + type Settings = Settings; + type Renderer = Renderer; + type Surface = Surface; + + fn new( + settings: Self::Settings, + _compatible_window: Option<&W>, + ) -> Result<(Self, Self::Renderer), Error> { + let (compositor, backend) = new(settings); + + Ok((compositor, Renderer::new(backend))) + } + + fn create_surface( + &mut self, + _window: &W, + ) -> Surface { + // TODO + Surface + } + + fn configure_surface( + &mut self, + _surface: &mut Surface, + _width: u32, + _height: u32, + ) { + // TODO + } + + fn fetch_information(&self) -> Information { + Information { + adapter: String::from("CPU"), + backend: String::from("tiny-skia"), + } + } + + fn present>( + &mut self, + renderer: &mut Self::Renderer, + surface: &mut Self::Surface, + viewport: &Viewport, + background_color: Color, + overlay: &[T], + ) -> Result<(), SurfaceError> { + renderer.with_primitives(|backend, primitives| { + present( + self, + backend, + surface, + primitives, + viewport, + background_color, + overlay, + ) + }) + } +} + +pub fn new(settings: Settings) -> (Compositor, Backend) { + // TODO + ( + Compositor { + _theme: PhantomData, + }, + Backend::new(settings), + ) +} + +pub fn present>( + _compositor: &mut Compositor, + _backend: &mut Backend, + _surface: &mut Surface, + _primitives: &[Primitive], + _viewport: &Viewport, + _background_color: Color, + _overlay: &[T], +) -> Result<(), compositor::SurfaceError> { + // TODO + Ok(()) +} -- cgit From 535d7a4d57e131e661587b36e41820dd6ccccc3e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 25 Feb 2023 16:05:42 +0100 Subject: Implement basic presentation with `softbuffer` for `iced_tiny_skia` --- tiny_skia/src/backend.rs | 19 ++++++++++- tiny_skia/src/window/compositor.rs | 66 ++++++++++++++++++++++++++++++-------- 2 files changed, 70 insertions(+), 15 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 4282a745..62373ec7 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -1,7 +1,8 @@ -use crate::{Font, Settings, Size}; +use crate::{Color, Font, Settings, Size, Viewport}; use iced_graphics::backend; use iced_graphics::text; +use iced_graphics::Primitive; use std::borrow::Cow; @@ -17,6 +18,22 @@ impl Backend { default_text_size: settings.default_text_size, } } + + pub fn draw>( + &mut self, + pixels: &mut tiny_skia::Pixmap, + _primitives: &[Primitive], + _viewport: &Viewport, + background_color: Color, + _overlay: &[T], + ) { + pixels.fill(into_color(background_color)); + } +} + +fn into_color(color: Color) -> tiny_skia::Color { + tiny_skia::Color::from_rgba(color.r, color.g, color.b, color.a) + .expect("Convert color from iced to tiny_skia") } impl iced_graphics::Backend for Backend { diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 053bb29b..164d99c6 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -10,7 +10,11 @@ pub struct Compositor { _theme: PhantomData, } -pub struct Surface; +pub struct Surface { + window: softbuffer::GraphicsContext, + pixels: tiny_skia::Pixmap, + buffer: Vec, +} impl iced_graphics::window::Compositor for Compositor { type Settings = Settings; @@ -28,19 +32,33 @@ impl iced_graphics::window::Compositor for Compositor { fn create_surface( &mut self, - _window: &W, + window: &W, + width: u32, + height: u32, ) -> Surface { - // TODO - Surface + let window = + unsafe { softbuffer::GraphicsContext::new(window, window) } + .expect("Create softbuffer for window"); + + let pixels = tiny_skia::Pixmap::new(width, height) + .expect("Create pixmap for window"); + + Surface { + window, + pixels, + buffer: vec![0; (width * height) as usize], + } } fn configure_surface( &mut self, - _surface: &mut Surface, - _width: u32, - _height: u32, + surface: &mut Surface, + width: u32, + height: u32, ) { - // TODO + surface.pixels = tiny_skia::Pixmap::new(width, height) + .expect("Create pixmap for window"); + surface.buffer = vec![0; (width * height) as usize]; } fn fetch_information(&self) -> Information { @@ -84,13 +102,33 @@ pub fn new(settings: Settings) -> (Compositor, Backend) { pub fn present>( _compositor: &mut Compositor, - _backend: &mut Backend, - _surface: &mut Surface, - _primitives: &[Primitive], - _viewport: &Viewport, - _background_color: Color, - _overlay: &[T], + backend: &mut Backend, + surface: &mut Surface, + primitives: &[Primitive], + viewport: &Viewport, + background_color: Color, + overlay: &[T], ) -> Result<(), compositor::SurfaceError> { + backend.draw( + &mut surface.pixels, + primitives, + viewport, + background_color, + overlay, + ); + + for (i, pixel) in surface.pixels.pixels_mut().iter().enumerate() { + surface.buffer[i] = u32::from(pixel.red()) << 16 + | u32::from(pixel.green()) << 8 + | u32::from(pixel.blue()); + } + + surface.window.set_buffer( + &surface.buffer, + surface.pixels.width() as u16, + surface.pixels.height() as u16, + ); + // TODO Ok(()) } -- cgit From 445b31c6c5f16ecc9f07bd072f246e827aa5b854 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 25 Feb 2023 21:06:58 +0100 Subject: Resize `Surface::buffer` instead of reallocating in `iced_tiny_skia` --- tiny_skia/src/window/compositor.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 164d99c6..8bb52a03 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -58,7 +58,8 @@ impl iced_graphics::window::Compositor for Compositor { ) { surface.pixels = tiny_skia::Pixmap::new(width, height) .expect("Create pixmap for window"); - surface.buffer = vec![0; (width * height) as usize]; + + surface.buffer.resize((width * height) as usize, 0); } fn fetch_information(&self) -> Information { -- cgit From df5d66423de141a009bbed993d99d491ed6373c9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 26 Feb 2023 00:38:46 +0100 Subject: Draft support for `Quad` and `Clip` primitives in `iced_tiny_skia` --- tiny_skia/src/backend.rs | 153 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 3 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 62373ec7..54752a21 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -2,7 +2,7 @@ use crate::{Color, Font, Settings, Size, Viewport}; use iced_graphics::backend; use iced_graphics::text; -use iced_graphics::Primitive; +use iced_graphics::{Background, Primitive, Rectangle, Vector}; use std::borrow::Cow; @@ -22,12 +22,133 @@ impl Backend { pub fn draw>( &mut self, pixels: &mut tiny_skia::Pixmap, - _primitives: &[Primitive], - _viewport: &Viewport, + primitives: &[Primitive], + viewport: &Viewport, background_color: Color, _overlay: &[T], ) { pixels.fill(into_color(background_color)); + + let scale_factor = viewport.scale_factor() as f32; + + for primitive in primitives { + draw_primitive(primitive, pixels, None, scale_factor, Vector::ZERO); + } + } +} + +fn draw_primitive( + primitive: &Primitive, + pixels: &mut tiny_skia::Pixmap, + clip_mask: Option<&tiny_skia::ClipMask>, + scale_factor: f32, + translation: Vector, +) { + match primitive { + Primitive::None => {} + Primitive::Quad { + bounds, + background, + border_radius: _, // TODO + border_width, + border_color, + } => { + let transform = tiny_skia::Transform::from_translate( + translation.x, + translation.y, + ) + .post_scale(scale_factor, scale_factor); + + let path = tiny_skia::PathBuilder::from_rect( + tiny_skia::Rect::from_xywh( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ) + .expect("Create quad rectangle"), + ); + + pixels.fill_path( + &path, + &tiny_skia::Paint { + shader: match background { + Background::Color(color) => { + tiny_skia::Shader::SolidColor(into_color(*color)) + } + }, + anti_alias: true, + ..tiny_skia::Paint::default() + }, + tiny_skia::FillRule::EvenOdd, + transform, + clip_mask, + ); + + if *border_width > 0.0 { + pixels.stroke_path( + &path, + &tiny_skia::Paint { + shader: tiny_skia::Shader::SolidColor(into_color( + *border_color, + )), + anti_alias: true, + ..tiny_skia::Paint::default() + }, + &tiny_skia::Stroke { + width: *border_width, + ..tiny_skia::Stroke::default() + }, + transform, + clip_mask, + ); + } + } + Primitive::Text { .. } => { + // TODO + } + Primitive::Image { .. } => { + // TODO + } + Primitive::Svg { .. } => { + // TODO + } + Primitive::Group { primitives } => { + for primitive in primitives { + draw_primitive( + primitive, + pixels, + clip_mask, + scale_factor, + translation, + ); + } + } + Primitive::Translate { + translation: offset, + content, + } => { + draw_primitive( + content, + pixels, + clip_mask, + scale_factor, + translation + *offset, + ); + } + Primitive::Clip { bounds, content } => { + draw_primitive( + content, + pixels, + Some(&rectangular_clip_mask(pixels, *bounds * scale_factor)), + scale_factor, + translation, + ); + } + Primitive::Cached { cache } => { + draw_primitive(cache, pixels, clip_mask, scale_factor, translation); + } + Primitive::SolidMesh { .. } | Primitive::GradientMesh { .. } => {} } } @@ -36,6 +157,32 @@ fn into_color(color: Color) -> tiny_skia::Color { .expect("Convert color from iced to tiny_skia") } +fn rectangular_clip_mask( + pixels: &tiny_skia::Pixmap, + bounds: Rectangle, +) -> tiny_skia::ClipMask { + let mut clip_mask = tiny_skia::ClipMask::new(); + + let path = { + let mut builder = tiny_skia::PathBuilder::new(); + builder.push_rect(bounds.x, bounds.y, bounds.width, bounds.height); + + builder.finish().unwrap() + }; + + clip_mask + .set_path( + pixels.width(), + pixels.height(), + &path, + tiny_skia::FillRule::EvenOdd, + true, + ) + .expect("Set path of clipping area"); + + clip_mask +} + impl iced_graphics::Backend for Backend { fn trim_measurements(&mut self) { // TODO -- cgit From 744f3028f484c44899fed56d9190387569828a95 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 26 Feb 2023 00:49:27 +0100 Subject: Use `Surface::buffer` directly for drawing in `iced_tiny_skia` ... with a nice little color trick :^) --- tiny_skia/src/backend.rs | 8 ++++---- tiny_skia/src/window/compositor.rs | 30 +++++++++++------------------- 2 files changed, 15 insertions(+), 23 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 54752a21..9eea1a32 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -21,7 +21,7 @@ impl Backend { pub fn draw>( &mut self, - pixels: &mut tiny_skia::Pixmap, + pixels: &mut tiny_skia::PixmapMut<'_>, primitives: &[Primitive], viewport: &Viewport, background_color: Color, @@ -39,7 +39,7 @@ impl Backend { fn draw_primitive( primitive: &Primitive, - pixels: &mut tiny_skia::Pixmap, + pixels: &mut tiny_skia::PixmapMut<'_>, clip_mask: Option<&tiny_skia::ClipMask>, scale_factor: f32, translation: Vector, @@ -153,12 +153,12 @@ fn draw_primitive( } fn into_color(color: Color) -> tiny_skia::Color { - tiny_skia::Color::from_rgba(color.r, color.g, color.b, color.a) + tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a) .expect("Convert color from iced to tiny_skia") } fn rectangular_clip_mask( - pixels: &tiny_skia::Pixmap, + pixels: &tiny_skia::PixmapMut<'_>, bounds: Rectangle, ) -> tiny_skia::ClipMask { let mut clip_mask = tiny_skia::ClipMask::new(); diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 8bb52a03..2bd5831e 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -12,7 +12,6 @@ pub struct Compositor { pub struct Surface { window: softbuffer::GraphicsContext, - pixels: tiny_skia::Pixmap, buffer: Vec, } @@ -40,13 +39,9 @@ impl iced_graphics::window::Compositor for Compositor { unsafe { softbuffer::GraphicsContext::new(window, window) } .expect("Create softbuffer for window"); - let pixels = tiny_skia::Pixmap::new(width, height) - .expect("Create pixmap for window"); - Surface { window, - pixels, - buffer: vec![0; (width * height) as usize], + buffer: vec![0; width as usize * height as usize], } } @@ -56,9 +51,6 @@ impl iced_graphics::window::Compositor for Compositor { width: u32, height: u32, ) { - surface.pixels = tiny_skia::Pixmap::new(width, height) - .expect("Create pixmap for window"); - surface.buffer.resize((width * height) as usize, 0); } @@ -110,26 +102,26 @@ pub fn present>( background_color: Color, overlay: &[T], ) -> Result<(), compositor::SurfaceError> { + let physical_size = viewport.physical_size(); + backend.draw( - &mut surface.pixels, + &mut tiny_skia::PixmapMut::from_bytes( + bytemuck::cast_slice_mut(&mut surface.buffer), + physical_size.width, + physical_size.height, + ) + .expect("Create pixel map"), primitives, viewport, background_color, overlay, ); - for (i, pixel) in surface.pixels.pixels_mut().iter().enumerate() { - surface.buffer[i] = u32::from(pixel.red()) << 16 - | u32::from(pixel.green()) << 8 - | u32::from(pixel.blue()); - } - surface.window.set_buffer( &surface.buffer, - surface.pixels.width() as u16, - surface.pixels.height() as u16, + physical_size.width as u16, + physical_size.height as u16, ); - // TODO Ok(()) } -- cgit From 64fb722dfe8769d4a92edb0133f1863383ecfd86 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 26 Feb 2023 23:40:17 +0100 Subject: Draft text support in `iced_tiny_skia` --- tiny_skia/src/backend.rs | 269 +++++++++++++++++++++--------------- tiny_skia/src/lib.rs | 1 + tiny_skia/src/text.rs | 350 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 508 insertions(+), 112 deletions(-) create mode 100644 tiny_skia/src/text.rs (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 9eea1a32..5e743479 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -9,6 +9,7 @@ use std::borrow::Cow; pub struct Backend { default_font: Font, default_text_size: f32, + text_pipeline: crate::text::Pipeline, } impl Backend { @@ -16,6 +17,7 @@ impl Backend { Self { default_font: settings.default_font, default_text_size: settings.default_text_size, + text_pipeline: crate::text::Pipeline::new(), } } @@ -32,123 +34,161 @@ impl Backend { let scale_factor = viewport.scale_factor() as f32; for primitive in primitives { - draw_primitive(primitive, pixels, None, scale_factor, Vector::ZERO); + self.draw_primitive( + primitive, + pixels, + None, + scale_factor, + Vector::ZERO, + ); } + + self.text_pipeline.end_frame(); } -} -fn draw_primitive( - primitive: &Primitive, - pixels: &mut tiny_skia::PixmapMut<'_>, - clip_mask: Option<&tiny_skia::ClipMask>, - scale_factor: f32, - translation: Vector, -) { - match primitive { - Primitive::None => {} - Primitive::Quad { - bounds, - background, - border_radius: _, // TODO - border_width, - border_color, - } => { - let transform = tiny_skia::Transform::from_translate( - translation.x, - translation.y, - ) - .post_scale(scale_factor, scale_factor); - - let path = tiny_skia::PathBuilder::from_rect( - tiny_skia::Rect::from_xywh( - bounds.x, - bounds.y, - bounds.width, - bounds.height, + fn draw_primitive( + &mut self, + primitive: &Primitive, + pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: Option<&tiny_skia::ClipMask>, + scale_factor: f32, + translation: Vector, + ) { + match primitive { + Primitive::None => {} + Primitive::Quad { + bounds, + background, + border_radius: _, // TODO + border_width, + border_color, + } => { + let transform = tiny_skia::Transform::from_translate( + translation.x, + translation.y, ) - .expect("Create quad rectangle"), - ); + .post_scale(scale_factor, scale_factor); - pixels.fill_path( - &path, - &tiny_skia::Paint { - shader: match background { - Background::Color(color) => { - tiny_skia::Shader::SolidColor(into_color(*color)) - } - }, - anti_alias: true, - ..tiny_skia::Paint::default() - }, - tiny_skia::FillRule::EvenOdd, - transform, - clip_mask, - ); + let path = tiny_skia::PathBuilder::from_rect( + tiny_skia::Rect::from_xywh( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ) + .expect("Create quad rectangle"), + ); - if *border_width > 0.0 { - pixels.stroke_path( + pixels.fill_path( &path, &tiny_skia::Paint { - shader: tiny_skia::Shader::SolidColor(into_color( - *border_color, - )), + shader: match background { + Background::Color(color) => { + tiny_skia::Shader::SolidColor(into_color( + *color, + )) + } + }, anti_alias: true, ..tiny_skia::Paint::default() }, - &tiny_skia::Stroke { - width: *border_width, - ..tiny_skia::Stroke::default() - }, + tiny_skia::FillRule::EvenOdd, transform, clip_mask, ); + + if *border_width > 0.0 { + pixels.stroke_path( + &path, + &tiny_skia::Paint { + shader: tiny_skia::Shader::SolidColor(into_color( + *border_color, + )), + anti_alias: true, + ..tiny_skia::Paint::default() + }, + &tiny_skia::Stroke { + width: *border_width, + ..tiny_skia::Stroke::default() + }, + transform, + clip_mask, + ); + } } - } - Primitive::Text { .. } => { - // TODO - } - Primitive::Image { .. } => { - // TODO - } - Primitive::Svg { .. } => { - // TODO - } - Primitive::Group { primitives } => { - for primitive in primitives { - draw_primitive( - primitive, + Primitive::Text { + content, + bounds, + color, + size, + font, + horizontal_alignment, + vertical_alignment, + } => { + self.text_pipeline.draw( + content, + (*bounds + translation) * scale_factor, + *color, + *size * scale_factor, + *font, + *horizontal_alignment, + *vertical_alignment, + pixels, + clip_mask, + ); + } + Primitive::Image { .. } => { + // TODO + } + Primitive::Svg { .. } => { + // TODO + } + Primitive::Group { primitives } => { + for primitive in primitives { + self.draw_primitive( + primitive, + pixels, + clip_mask, + scale_factor, + translation, + ); + } + } + Primitive::Translate { + translation: offset, + content, + } => { + self.draw_primitive( + content, pixels, clip_mask, scale_factor, + translation + *offset, + ); + } + Primitive::Clip { bounds, content } => { + self.draw_primitive( + content, + pixels, + Some(&rectangular_clip_mask( + pixels, + *bounds * scale_factor, + )), + scale_factor, translation, ); } + Primitive::Cached { cache } => { + self.draw_primitive( + cache, + pixels, + clip_mask, + scale_factor, + translation, + ); + } + Primitive::SolidMesh { .. } | Primitive::GradientMesh { .. } => {} } - Primitive::Translate { - translation: offset, - content, - } => { - draw_primitive( - content, - pixels, - clip_mask, - scale_factor, - translation + *offset, - ); - } - Primitive::Clip { bounds, content } => { - draw_primitive( - content, - pixels, - Some(&rectangular_clip_mask(pixels, *bounds * scale_factor)), - scale_factor, - translation, - ); - } - Primitive::Cached { cache } => { - draw_primitive(cache, pixels, clip_mask, scale_factor, translation); - } - Primitive::SolidMesh { .. } | Primitive::GradientMesh { .. } => {} } } @@ -185,7 +225,7 @@ fn rectangular_clip_mask( impl iced_graphics::Backend for Backend { fn trim_measurements(&mut self) { - // TODO + self.text_pipeline.trim_measurement_cache(); } } @@ -204,30 +244,35 @@ impl backend::Text for Backend { fn measure( &self, - _contents: &str, - _size: f32, - _font: Font, - _bounds: Size, + contents: &str, + size: f32, + font: Font, + bounds: Size, ) -> (f32, f32) { - // TODO - (0.0, 0.0) + self.text_pipeline.measure(contents, size, font, bounds) } fn hit_test( &self, - _contents: &str, - _size: f32, - _font: Font, - _bounds: Size, - _point: iced_native::Point, - _nearest_only: bool, + contents: &str, + size: f32, + font: Font, + bounds: Size, + point: iced_native::Point, + nearest_only: bool, ) -> Option { - // TODO - None + self.text_pipeline.hit_test( + contents, + size, + font, + bounds, + point, + nearest_only, + ) } - fn load_font(&mut self, _font: Cow<'static, [u8]>) { - // TODO + fn load_font(&mut self, font: Cow<'static, [u8]>) { + self.text_pipeline.load_font(font); } } diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index fce44e9e..420a1ffb 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -2,6 +2,7 @@ pub mod window; mod backend; mod settings; +mod text; pub use backend::Backend; pub use settings::Settings; diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs new file mode 100644 index 00000000..5bd6eff1 --- /dev/null +++ b/tiny_skia/src/text.rs @@ -0,0 +1,350 @@ +pub use iced_native::text::Hit; + +use iced_native::alignment; +use iced_native::{Color, Font, Rectangle, Size}; + +use rustc_hash::{FxHashMap, FxHashSet}; +use std::borrow::Cow; +use std::cell::RefCell; +use std::collections::hash_map; +use std::hash::{BuildHasher, Hash, Hasher}; +use std::sync::Arc; + +#[allow(missing_debug_implementations)] +pub struct Pipeline { + system: Option, +} + +#[ouroboros::self_referencing] +struct System { + fonts: cosmic_text::FontSystem, + + #[borrows(fonts)] + #[not_covariant] + measurement_cache: RefCell>, + + #[borrows(fonts)] + #[not_covariant] + render_cache: Cache<'this>, +} + +impl Pipeline { + pub fn new() -> Self { + Pipeline { + system: Some( + SystemBuilder { + fonts: cosmic_text::FontSystem::new_with_fonts( + [cosmic_text::fontdb::Source::Binary(Arc::new( + include_bytes!("../../wgpu/fonts/Iced-Icons.ttf") + .as_slice(), + ))] + .into_iter(), + ), + measurement_cache_builder: |_| RefCell::new(Cache::new()), + render_cache_builder: |_| Cache::new(), + } + .build(), + ), + } + } + + pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { + let heads = self.system.take().unwrap().into_heads(); + + let (locale, mut db) = heads.fonts.into_locale_and_db(); + + db.load_font_source(cosmic_text::fontdb::Source::Binary(Arc::new( + bytes.into_owned(), + ))); + + self.system = Some( + SystemBuilder { + fonts: cosmic_text::FontSystem::new_with_locale_and_db( + locale, db, + ), + measurement_cache_builder: |_| RefCell::new(Cache::new()), + render_cache_builder: |_| Cache::new(), + } + .build(), + ); + } + + pub fn draw( + &mut self, + content: &str, + bounds: Rectangle, + color: Color, + size: f32, + font: Font, + _horizontal_alignment: alignment::Horizontal, // TODO + _vertical_alignment: alignment::Vertical, // TODO + pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: Option<&tiny_skia::ClipMask>, + ) { + self.system.as_mut().unwrap().with_mut(|fields| { + let key = Key { + bounds: bounds.size(), + content, + font, + size, + }; + + let (_, buffer) = fields.render_cache.allocate(&fields.fonts, key); + + let mut swash = cosmic_text::SwashCache::new(&fields.fonts); + + for run in buffer.layout_runs() { + for glyph in run.glyphs { + // TODO: Outline support + if let Some(image) = swash.get_image(glyph.cache_key) { + let glyph_size = image.placement.width as usize + * image.placement.height as usize; + + if glyph_size == 0 { + continue; + } + + // TODO: Cache glyph rasterization + let mut buffer = vec![0u32; glyph_size]; + + match image.content { + cosmic_text::SwashContent::Mask => { + let mut i = 0; + + // TODO: Blend alpha + let [r, g, b, _a] = color.into_rgba8(); + + for _y in 0..image.placement.height { + for _x in 0..image.placement.width { + buffer[i] = + tiny_skia::ColorU8::from_rgba( + b, + g, + r, + image.data[i], + ) + .premultiply() + .get(); + + i += 1; + } + } + } + cosmic_text::SwashContent::Color => { + let mut i = 0; + + for _y in 0..image.placement.height { + for _x in 0..image.placement.width { + // TODO: Blend alpha + buffer[i] = (image.data[i + 3] as u32) + << 24 + | (image.data[i + 2] as u32) << 16 + | (image.data[i + 1] as u32) << 8 + | image.data[i] as u32; + + i += 1; + } + } + } + cosmic_text::SwashContent::SubpixelMask => { + // TODO + } + } + + let pixmap = tiny_skia::PixmapRef::from_bytes( + bytemuck::cast_slice(&buffer), + image.placement.width, + image.placement.height, + ) + .expect("Create glyph pixel map"); + + pixels.draw_pixmap( + bounds.x as i32 + + glyph.x_int + + image.placement.left, + bounds.y as i32 - glyph.y_int - image.placement.top + + run.line_y as i32, + pixmap, + &tiny_skia::PixmapPaint::default(), + tiny_skia::Transform::identity(), + clip_mask, + ); + } + } + } + }); + } + + pub fn end_frame(&mut self) { + self.system + .as_mut() + .unwrap() + .with_render_cache_mut(|cache| cache.trim()); + } + + pub fn measure( + &self, + content: &str, + size: f32, + font: Font, + bounds: Size, + ) -> (f32, f32) { + self.system.as_ref().unwrap().with(|fields| { + let mut measurement_cache = fields.measurement_cache.borrow_mut(); + + let (_, paragraph) = measurement_cache.allocate( + fields.fonts, + Key { + content, + size, + font, + bounds, + }, + ); + + let (total_lines, max_width) = paragraph + .layout_runs() + .enumerate() + .fold((0, 0.0), |(_, max), (i, buffer)| { + (i + 1, buffer.line_w.max(max)) + }); + + (max_width, size * 1.2 * total_lines as f32) + }) + } + + pub fn hit_test( + &self, + content: &str, + size: f32, + font: iced_native::Font, + bounds: iced_native::Size, + point: iced_native::Point, + _nearest_only: bool, + ) -> Option { + self.system.as_ref().unwrap().with(|fields| { + let mut measurement_cache = fields.measurement_cache.borrow_mut(); + + let (_, paragraph) = measurement_cache.allocate( + fields.fonts, + Key { + content, + size, + font, + bounds, + }, + ); + + let cursor = paragraph.hit(point.x, point.y)?; + + Some(Hit::CharOffset(cursor.index)) + }) + } + + pub fn trim_measurement_cache(&mut self) { + self.system + .as_mut() + .unwrap() + .with_measurement_cache_mut(|cache| cache.borrow_mut().trim()); + } +} + +fn to_family(font: Font) -> cosmic_text::Family<'static> { + match font { + Font::Name(name) => cosmic_text::Family::Name(name), + Font::SansSerif => cosmic_text::Family::SansSerif, + Font::Serif => cosmic_text::Family::Serif, + Font::Cursive => cosmic_text::Family::Cursive, + Font::Fantasy => cosmic_text::Family::Fantasy, + Font::Monospace => cosmic_text::Family::Monospace, + } +} + +struct Cache<'a> { + entries: FxHashMap>, + recently_used: FxHashSet, + hasher: HashBuilder, + trim_count: usize, +} + +#[cfg(not(target_arch = "wasm32"))] +type HashBuilder = twox_hash::RandomXxHashBuilder64; + +#[cfg(target_arch = "wasm32")] +type HashBuilder = std::hash::BuildHasherDefault; + +impl<'a> Cache<'a> { + const TRIM_INTERVAL: usize = 300; + + fn new() -> Self { + Self { + entries: FxHashMap::default(), + recently_used: FxHashSet::default(), + hasher: HashBuilder::default(), + trim_count: 0, + } + } + + fn allocate( + &mut self, + fonts: &'a cosmic_text::FontSystem, + key: Key<'_>, + ) -> (KeyHash, &mut cosmic_text::Buffer<'a>) { + let hash = { + let mut hasher = self.hasher.build_hasher(); + + key.content.hash(&mut hasher); + key.size.to_bits().hash(&mut hasher); + key.font.hash(&mut hasher); + key.bounds.width.to_bits().hash(&mut hasher); + key.bounds.height.to_bits().hash(&mut hasher); + + hasher.finish() + }; + + if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) { + let metrics = cosmic_text::Metrics::new(key.size, key.size * 1.2); + let mut buffer = cosmic_text::Buffer::new(fonts, metrics); + + buffer.set_size( + key.bounds.width, + key.bounds.height.max(key.size * 1.2), + ); + buffer.set_text( + key.content, + cosmic_text::Attrs::new() + .family(to_family(key.font)) + .monospaced(matches!(key.font, Font::Monospace)), + ); + + let _ = entry.insert(buffer); + } + + let _ = self.recently_used.insert(hash); + + (hash, self.entries.get_mut(&hash).unwrap()) + } + + fn trim(&mut self) { + if self.trim_count >= Self::TRIM_INTERVAL { + self.entries + .retain(|key, _| self.recently_used.contains(key)); + + self.recently_used.clear(); + + self.trim_count = 0; + } else { + self.trim_count += 1; + } + } +} + +#[derive(Debug, Clone, Copy)] +struct Key<'a> { + content: &'a str, + size: f32, + font: Font, + bounds: Size, +} + +type KeyHash = u64; -- cgit From 3386402f5a3e75cdacd230f5e76cd54f4868d87d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 26 Feb 2023 23:44:50 +0100 Subject: Implement text alignment support in `iced_tiny_skia` --- tiny_skia/src/text.rs | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 5bd6eff1..64f31aae 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -76,8 +76,8 @@ impl Pipeline { color: Color, size: f32, font: Font, - _horizontal_alignment: alignment::Horizontal, // TODO - _vertical_alignment: alignment::Vertical, // TODO + horizontal_alignment: alignment::Horizontal, + vertical_alignment: alignment::Vertical, pixels: &mut tiny_skia::PixmapMut<'_>, clip_mask: Option<&tiny_skia::ClipMask>, ) { @@ -91,6 +91,27 @@ impl Pipeline { let (_, buffer) = fields.render_cache.allocate(&fields.fonts, key); + let (total_lines, max_width) = buffer + .layout_runs() + .enumerate() + .fold((0, 0.0), |(_, max), (i, buffer)| { + (i + 1, buffer.line_w.max(max)) + }); + + let total_height = total_lines as f32 * size * 1.2; + + let x = match horizontal_alignment { + alignment::Horizontal::Left => bounds.x, + alignment::Horizontal::Center => bounds.x - max_width / 2.0, + alignment::Horizontal::Right => bounds.x - max_width, + }; + + let y = match vertical_alignment { + alignment::Vertical::Top => bounds.y, + alignment::Vertical::Center => bounds.y - total_height / 2.0, + alignment::Vertical::Bottom => bounds.y - total_height, + }; + let mut swash = cosmic_text::SwashCache::new(&fields.fonts); for run in buffer.layout_runs() { @@ -159,10 +180,8 @@ impl Pipeline { .expect("Create glyph pixel map"); pixels.draw_pixmap( - bounds.x as i32 - + glyph.x_int - + image.placement.left, - bounds.y as i32 - glyph.y_int - image.placement.top + x as i32 + glyph.x_int + image.placement.left, + y as i32 - glyph.y_int - image.placement.top + run.line_y as i32, pixmap, &tiny_skia::PixmapPaint::default(), -- cgit From 4067c427db19eb59c4ec6c8c6d6658a9643df580 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 26 Feb 2023 23:49:58 +0100 Subject: Fix glyphs with color mask in `iced_tiny_skia` --- tiny_skia/src/text.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 64f31aae..fc26703b 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -157,13 +157,17 @@ impl Pipeline { for _y in 0..image.placement.height { for _x in 0..image.placement.width { // TODO: Blend alpha - buffer[i] = (image.data[i + 3] as u32) - << 24 - | (image.data[i + 2] as u32) << 16 - | (image.data[i + 1] as u32) << 8 - | image.data[i] as u32; + buffer[i >> 2] = + tiny_skia::ColorU8::from_rgba( + image.data[i + 2], + image.data[i + 1], + image.data[i], + image.data[i + 3], + ) + .premultiply() + .get(); - i += 1; + i += 4; } } } -- cgit From 53573cf7cfd48f9bfd97d9e8b82308a0290d2b9d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 26 Feb 2023 23:59:00 +0100 Subject: Draw debug overlay in `iced_tiny_skia` --- tiny_skia/src/backend.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 5e743479..cefed71f 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -1,5 +1,6 @@ use crate::{Color, Font, Settings, Size, Viewport}; +use iced_graphics::alignment; use iced_graphics::backend; use iced_graphics::text; use iced_graphics::{Background, Primitive, Rectangle, Vector}; @@ -27,7 +28,7 @@ impl Backend { primitives: &[Primitive], viewport: &Viewport, background_color: Color, - _overlay: &[T], + overlay: &[T], ) { pixels.fill(into_color(background_color)); @@ -43,6 +44,31 @@ impl Backend { ); } + for (i, text) in overlay.iter().enumerate() { + const OVERLAY_TEXT_SIZE: f32 = 20.0; + + self.draw_primitive( + &Primitive::Text { + content: text.as_ref().to_owned(), + size: OVERLAY_TEXT_SIZE, + bounds: Rectangle { + x: 10.0, + y: 10.0 + i as f32 * OVERLAY_TEXT_SIZE * 1.2, + width: f32::INFINITY, + height: f32::INFINITY, + }, + color: Color::BLACK, + font: Font::Monospace, + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + }, + pixels, + None, + scale_factor, + Vector::ZERO, + ); + } + self.text_pipeline.end_frame(); } -- cgit From fbb14bf9b879d3d154618fa8d6a81bac018fee69 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 27 Feb 2023 00:47:53 +0100 Subject: Implement `border_radius` support for quads in `iced_tiny_skia` --- tiny_skia/src/backend.rs | 80 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 70 insertions(+), 10 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index cefed71f..b1dd6a46 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -85,7 +85,7 @@ impl Backend { Primitive::Quad { bounds, background, - border_radius: _, // TODO + border_radius, border_width, border_color, } => { @@ -95,15 +95,7 @@ impl Backend { ) .post_scale(scale_factor, scale_factor); - let path = tiny_skia::PathBuilder::from_rect( - tiny_skia::Rect::from_xywh( - bounds.x, - bounds.y, - bounds.width, - bounds.height, - ) - .expect("Create quad rectangle"), - ); + let path = rounded_rectangle(*bounds, *border_radius); pixels.fill_path( &path, @@ -223,6 +215,74 @@ fn into_color(color: Color) -> tiny_skia::Color { .expect("Convert color from iced to tiny_skia") } +fn rounded_rectangle( + bounds: Rectangle, + border_radius: [f32; 4], +) -> tiny_skia::Path { + let [top_left, top_right, bottom_right, bottom_left] = border_radius; + + if top_left == top_right + && top_left == bottom_right + && top_left == bottom_left + && top_left == bounds.width / 2.0 + && top_left == bounds.height / 2.0 + { + return tiny_skia::PathBuilder::from_circle( + bounds.x + bounds.width / 2.0, + bounds.y + bounds.height / 2.0, + top_left, + ) + .expect("Build circle path"); + } + + let mut builder = tiny_skia::PathBuilder::new(); + + builder.move_to(bounds.x + top_left, bounds.y); + builder.line_to(bounds.x + bounds.width - top_right, bounds.y); + + if top_right > 0.0 { + builder.quad_to( + bounds.x + bounds.width, + bounds.y, + bounds.x + bounds.width, + bounds.y + top_right, + ); + } + + builder.line_to( + bounds.x + bounds.width, + bounds.y + bounds.height - bottom_right, + ); + + if bottom_right > 0.0 { + builder.quad_to( + bounds.x + bounds.width, + bounds.y + bounds.height, + bounds.x + bounds.width - bottom_right, + bounds.y + bounds.height, + ); + } + + builder.line_to(bounds.x + bottom_left, bounds.y + bounds.height); + + if bottom_right > 0.0 { + builder.quad_to( + bounds.x, + bounds.y + bounds.height, + bounds.x, + bounds.y + bounds.height - bottom_left, + ); + } + + builder.line_to(bounds.x, bounds.y + top_left); + + if top_left > 0.0 { + builder.quad_to(bounds.x, bounds.y, bounds.x + top_left, bounds.y); + } + + builder.finish().expect("Build rounded rectangle path") +} + fn rectangular_clip_mask( pixels: &tiny_skia::PixmapMut<'_>, bounds: Rectangle, -- cgit From 4e615a65cab9dfc5fa4a17a72580c573c1c040d9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 27 Feb 2023 01:12:06 +0100 Subject: Fix `clippy` lints --- tiny_skia/src/text.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index fc26703b..6d4cfe96 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -89,7 +89,7 @@ impl Pipeline { size, }; - let (_, buffer) = fields.render_cache.allocate(&fields.fonts, key); + let (_, buffer) = fields.render_cache.allocate(fields.fonts, key); let (total_lines, max_width) = buffer .layout_runs() @@ -112,7 +112,7 @@ impl Pipeline { alignment::Vertical::Bottom => bounds.y - total_height, }; - let mut swash = cosmic_text::SwashCache::new(&fields.fonts); + let mut swash = cosmic_text::SwashCache::new(fields.fonts); for run in buffer.layout_runs() { for glyph in run.glyphs { -- cgit From 37ce30f360ce7cba9ad05654e1faf26276a1dc17 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 27 Feb 2023 02:58:02 +0100 Subject: Use `kurbo` to approximate arcs in `iced_tiny_skia` --- tiny_skia/src/backend.rs | 80 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 70 insertions(+), 10 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index b1dd6a46..38a6c51d 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -241,48 +241,108 @@ fn rounded_rectangle( builder.line_to(bounds.x + bounds.width - top_right, bounds.y); if top_right > 0.0 { - builder.quad_to( - bounds.x + bounds.width, + arc_to( + &mut builder, + bounds.x + bounds.width - top_right, bounds.y, bounds.x + bounds.width, bounds.y + top_right, + top_right, ); } - builder.line_to( + maybe_line_to( + &mut builder, bounds.x + bounds.width, bounds.y + bounds.height - bottom_right, ); if bottom_right > 0.0 { - builder.quad_to( + arc_to( + &mut builder, bounds.x + bounds.width, - bounds.y + bounds.height, + bounds.y + bounds.height - bottom_right, bounds.x + bounds.width - bottom_right, bounds.y + bounds.height, + bottom_right, ); } - builder.line_to(bounds.x + bottom_left, bounds.y + bounds.height); + maybe_line_to( + &mut builder, + bounds.x + bottom_left, + bounds.y + bounds.height, + ); if bottom_right > 0.0 { - builder.quad_to( - bounds.x, + arc_to( + &mut builder, + bounds.x + bottom_left, bounds.y + bounds.height, bounds.x, bounds.y + bounds.height - bottom_left, + bottom_left, ); } - builder.line_to(bounds.x, bounds.y + top_left); + maybe_line_to(&mut builder, bounds.x, bounds.y + top_left); if top_left > 0.0 { - builder.quad_to(bounds.x, bounds.y, bounds.x + top_left, bounds.y); + arc_to( + &mut builder, + bounds.x, + bounds.y + top_left, + bounds.x + top_left, + bounds.y, + top_left, + ); } builder.finish().expect("Build rounded rectangle path") } +fn maybe_line_to(path: &mut tiny_skia::PathBuilder, x: f32, y: f32) { + if path.last_point() != Some(tiny_skia::Point { x, y }) { + path.line_to(x, y); + } +} + +fn arc_to( + path: &mut tiny_skia::PathBuilder, + x_from: f32, + y_from: f32, + x_to: f32, + y_to: f32, + radius: f32, +) { + let svg_arc = kurbo::SvgArc { + from: kurbo::Point::new(f64::from(x_from), f64::from(y_from)), + to: kurbo::Point::new(f64::from(x_to), f64::from(y_to)), + radii: kurbo::Vec2::new(f64::from(radius), f64::from(radius)), + x_rotation: 0.0, + large_arc: false, + sweep: true, + }; + + match kurbo::Arc::from_svg_arc(&svg_arc) { + Some(arc) => { + arc.to_cubic_beziers(0.1, |p1, p2, p| { + path.cubic_to( + p1.x as f32, + p1.y as f32, + p2.x as f32, + p2.y as f32, + p.x as f32, + p.y as f32, + ); + }); + } + None => { + path.line_to(x_to as f32, y_to as f32); + } + } +} + fn rectangular_clip_mask( pixels: &tiny_skia::PixmapMut<'_>, bounds: Rectangle, -- cgit From 8750d83337041f1d0fac8c2c0f4c40fd3a051a2c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 27 Feb 2023 03:02:13 +0100 Subject: Short-circuit rectangle path building in `iced_tiny_skia` --- tiny_skia/src/backend.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 38a6c51d..d0977462 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -221,6 +221,22 @@ fn rounded_rectangle( ) -> tiny_skia::Path { let [top_left, top_right, bottom_right, bottom_left] = border_radius; + if top_left == 0.0 + && top_right == 0.0 + && bottom_right == 0.0 + && bottom_left == 0.0 + { + return tiny_skia::PathBuilder::from_rect( + tiny_skia::Rect::from_xywh( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ) + .expect("Build quad rectangle"), + ); + } + if top_left == top_right && top_left == bottom_right && top_left == bottom_left -- cgit From 3105ad2e0036e101e66b5e6ab437b570f2d923a3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 27 Feb 2023 03:04:05 +0100 Subject: Remove useless `f32` conversion in `iced_tiny_skia` --- tiny_skia/src/backend.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index d0977462..66d83221 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -354,7 +354,7 @@ fn arc_to( }); } None => { - path.line_to(x_to as f32, y_to as f32); + path.line_to(x_to, y_to); } } } -- cgit From c1ff803b8f98beb2a73bb4252b34921110aa6cf0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 27 Feb 2023 16:28:19 +0100 Subject: Implement basic glyph cache in `iced_tiny_skia` --- tiny_skia/src/text.rs | 183 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 119 insertions(+), 64 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 6d4cfe96..cd7eb1ed 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -13,6 +13,7 @@ use std::sync::Arc; #[allow(missing_debug_implementations)] pub struct Pipeline { system: Option, + glyph_cache: GlyphCache, } #[ouroboros::self_referencing] @@ -45,6 +46,7 @@ impl Pipeline { } .build(), ), + glyph_cache: GlyphCache::new(), } } @@ -116,76 +118,20 @@ impl Pipeline { for run in buffer.layout_runs() { for glyph in run.glyphs { - // TODO: Outline support - if let Some(image) = swash.get_image(glyph.cache_key) { - let glyph_size = image.placement.width as usize - * image.placement.height as usize; - - if glyph_size == 0 { - continue; - } - - // TODO: Cache glyph rasterization - let mut buffer = vec![0u32; glyph_size]; - - match image.content { - cosmic_text::SwashContent::Mask => { - let mut i = 0; - - // TODO: Blend alpha - let [r, g, b, _a] = color.into_rgba8(); - - for _y in 0..image.placement.height { - for _x in 0..image.placement.width { - buffer[i] = - tiny_skia::ColorU8::from_rgba( - b, - g, - r, - image.data[i], - ) - .premultiply() - .get(); - - i += 1; - } - } - } - cosmic_text::SwashContent::Color => { - let mut i = 0; - - for _y in 0..image.placement.height { - for _x in 0..image.placement.width { - // TODO: Blend alpha - buffer[i >> 2] = - tiny_skia::ColorU8::from_rgba( - image.data[i + 2], - image.data[i + 1], - image.data[i], - image.data[i + 3], - ) - .premultiply() - .get(); - - i += 4; - } - } - } - cosmic_text::SwashContent::SubpixelMask => { - // TODO - } - } - + if let Some((buffer, placement)) = self + .glyph_cache + .allocate(glyph.cache_key, color, &mut swash) + { let pixmap = tiny_skia::PixmapRef::from_bytes( bytemuck::cast_slice(&buffer), - image.placement.width, - image.placement.height, + placement.width, + placement.height, ) .expect("Create glyph pixel map"); pixels.draw_pixmap( - x as i32 + glyph.x_int + image.placement.left, - y as i32 - glyph.y_int - image.placement.top + x as i32 + glyph.x_int + placement.left, + y as i32 - glyph.y_int - placement.top + run.line_y as i32, pixmap, &tiny_skia::PixmapPaint::default(), @@ -203,6 +149,8 @@ impl Pipeline { .as_mut() .unwrap() .with_render_cache_mut(|cache| cache.trim()); + + self.glyph_cache.trim(); } pub fn measure( @@ -283,6 +231,113 @@ fn to_family(font: Font) -> cosmic_text::Family<'static> { } } +#[derive(Debug, Clone, Default)] +struct GlyphCache { + entries: FxHashMap< + (cosmic_text::CacheKey, [u8; 3]), + (Vec, cosmic_text::Placement), + >, + recently_used: FxHashSet<(cosmic_text::CacheKey, [u8; 3])>, + trim_count: usize, +} + +impl GlyphCache { + fn new() -> Self { + GlyphCache::default() + } + + fn allocate( + &mut self, + cache_key: cosmic_text::CacheKey, + color: Color, + swash: &mut cosmic_text::SwashCache<'_>, + ) -> Option<(&[u8], cosmic_text::Placement)> { + let [r, g, b, _a] = color.into_rgba8(); + let key = (cache_key, [r, g, b]); + + if let hash_map::Entry::Vacant(entry) = self.entries.entry(key) { + // TODO: Outline support + let image = swash.get_image(cache_key).as_ref()?; + + let glyph_size = image.placement.width as usize + * image.placement.height as usize; + + if glyph_size == 0 { + return None; + } + + // TODO: Cache glyph rasterization + let mut buffer = vec![0u32; glyph_size]; + + match image.content { + cosmic_text::SwashContent::Mask => { + let mut i = 0; + + // TODO: Blend alpha + + for _y in 0..image.placement.height { + for _x in 0..image.placement.width { + buffer[i] = tiny_skia::ColorU8::from_rgba( + b, + g, + r, + image.data[i], + ) + .premultiply() + .get(); + + i += 1; + } + } + } + cosmic_text::SwashContent::Color => { + let mut i = 0; + + for _y in 0..image.placement.height { + for _x in 0..image.placement.width { + // TODO: Blend alpha + buffer[i >> 2] = tiny_skia::ColorU8::from_rgba( + image.data[i + 2], + image.data[i + 1], + image.data[i], + image.data[i + 3], + ) + .premultiply() + .get(); + + i += 4; + } + } + } + cosmic_text::SwashContent::SubpixelMask => { + // TODO + } + } + + entry.insert((buffer, image.placement)); + } + + self.recently_used.insert(key); + + self.entries.get(&key).map(|(buffer, placement)| { + (bytemuck::cast_slice(buffer.as_slice()), *placement) + }) + } + + pub fn trim(&mut self) { + if self.trim_count > 300 { + self.entries + .retain(|key, _| self.recently_used.contains(key)); + + self.recently_used.clear(); + + self.trim_count = 0; + } else { + self.trim_count += 1; + } + } +} + struct Cache<'a> { entries: FxHashMap>, recently_used: FxHashSet, -- cgit From 151daf95b70d5a53007496e58f49fc618c1a22e4 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 27 Feb 2023 16:30:54 +0100 Subject: Remove unnecessary `cast_slice` in `iced_tiny_skia` --- tiny_skia/src/text.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index cd7eb1ed..37b0fe47 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -123,7 +123,7 @@ impl Pipeline { .allocate(glyph.cache_key, color, &mut swash) { let pixmap = tiny_skia::PixmapRef::from_bytes( - bytemuck::cast_slice(&buffer), + buffer, placement.width, placement.height, ) @@ -266,7 +266,6 @@ impl GlyphCache { return None; } - // TODO: Cache glyph rasterization let mut buffer = vec![0u32; glyph_size]; match image.content { -- cgit From fd06de5d9c5afa05c5b11cda730b0769aef92caa Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 28 Feb 2023 03:48:34 +0100 Subject: Use `get_image_uncached` in `iced_tiny_skia` ... since we are not reusing the `SwashCache` --- tiny_skia/src/text.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 37b0fe47..da3a06bf 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -257,7 +257,7 @@ impl GlyphCache { if let hash_map::Entry::Vacant(entry) = self.entries.entry(key) { // TODO: Outline support - let image = swash.get_image(cache_key).as_ref()?; + let image = swash.get_image_uncached(cache_key)?; let glyph_size = image.placement.width as usize * image.placement.height as usize; -- cgit From 5fd5d1cdf8e5354788dc40729c4565ef377d3bba Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 Mar 2023 21:34:26 +0100 Subject: Implement `Canvas` support for `iced_tiny_skia` --- tiny_skia/src/backend.rs | 48 ++++++- tiny_skia/src/canvas.rs | 276 +++++++++++++++++++++++++++++++++++++ tiny_skia/src/lib.rs | 10 +- tiny_skia/src/primitive.rs | 82 +++++++++++ tiny_skia/src/window/compositor.rs | 3 +- 5 files changed, 410 insertions(+), 9 deletions(-) create mode 100644 tiny_skia/src/canvas.rs create mode 100644 tiny_skia/src/primitive.rs (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 66d83221..e08cede7 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -1,9 +1,9 @@ -use crate::{Color, Font, Settings, Size, Viewport}; +use crate::{Color, Font, Primitive, Settings, Size, Viewport}; use iced_graphics::alignment; use iced_graphics::backend; use iced_graphics::text; -use iced_graphics::{Background, Primitive, Rectangle, Vector}; +use iced_graphics::{Background, Rectangle, Vector}; use std::borrow::Cow; @@ -81,7 +81,6 @@ impl Backend { translation: Vector, ) { match primitive { - Primitive::None => {} Primitive::Quad { bounds, background, @@ -161,6 +160,38 @@ impl Backend { Primitive::Svg { .. } => { // TODO } + Primitive::Fill { + path, + paint, + rule, + transform, + } => { + pixels.fill_path( + path, + paint, + *rule, + transform + .post_translate(translation.x, translation.y) + .post_scale(scale_factor, scale_factor), + clip_mask, + ); + } + Primitive::Stroke { + path, + paint, + stroke, + transform, + } => { + pixels.stroke_path( + path, + paint, + stroke, + transform + .post_translate(translation.x, translation.y) + .post_scale(scale_factor, scale_factor), + clip_mask, + ); + } Primitive::Group { primitives } => { for primitive in primitives { self.draw_primitive( @@ -196,16 +227,19 @@ impl Backend { translation, ); } - Primitive::Cached { cache } => { + Primitive::Cache { content } => { self.draw_primitive( - cache, + content, pixels, clip_mask, scale_factor, translation, ); } - Primitive::SolidMesh { .. } | Primitive::GradientMesh { .. } => {} + Primitive::SolidMesh { .. } | Primitive::GradientMesh { .. } => { + // Not supported! + // TODO: Draw a placeholder (?) / Log it (?) + } } } } @@ -386,6 +420,8 @@ fn rectangular_clip_mask( } impl iced_graphics::Backend for Backend { + type Geometry = (); + fn trim_measurements(&mut self) { self.text_pipeline.trim_measurement_cache(); } diff --git a/tiny_skia/src/canvas.rs b/tiny_skia/src/canvas.rs new file mode 100644 index 00000000..c3b8b316 --- /dev/null +++ b/tiny_skia/src/canvas.rs @@ -0,0 +1,276 @@ +use crate::{Point, Primitive, Rectangle, Size, Vector}; + +use iced_native::widget::canvas::fill::{self, Fill}; +use iced_native::widget::canvas::stroke::{self, Stroke}; +use iced_native::widget::canvas::{Path, Style, Text}; +use iced_native::Gradient; + +pub struct Frame { + size: Size, + transform: tiny_skia::Transform, + stack: Vec, + primitives: Vec, +} + +impl Frame { + pub fn new(size: Size) -> Self { + Self { + size, + transform: tiny_skia::Transform::identity(), + stack: Vec::new(), + primitives: Vec::new(), + } + } + + pub fn width(&self) -> f32 { + self.size.width + } + + pub fn height(&self) -> f32 { + self.size.height + } + + pub fn size(&self) -> Size { + self.size + } + + pub fn center(&self) -> Point { + Point::new(self.size.width / 2.0, self.size.height / 2.0) + } + + pub fn fill(&mut self, path: &Path, fill: impl Into) { + let path = convert_path(path); + let fill = fill.into(); + + self.primitives.push(Primitive::Fill { + path, + paint: into_paint(fill.style), + rule: into_fill_rule(fill.rule), + transform: self.transform, + }); + } + + pub fn fill_rectangle( + &mut self, + top_left: Point, + size: Size, + fill: impl Into, + ) { + self.fill(&Path::rectangle(top_left, size), fill); + } + + pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { + let path = convert_path(path); + let stroke = stroke.into(); + let skia_stroke = into_stroke(&stroke); + + self.primitives.push(Primitive::Stroke { + path, + paint: into_paint(stroke.style), + stroke: skia_stroke, + transform: self.transform, + }); + } + + pub fn fill_text(&mut self, text: impl Into) { + let text = text.into(); + + let position = if self.transform.is_identity() { + text.position + } else { + let mut transformed = [tiny_skia::Point { + x: text.position.x, + y: text.position.y, + }]; + + self.transform.map_points(&mut transformed); + + Point::new(transformed[0].x, transformed[0].y) + }; + + // TODO: Use vectorial text instead of primitive + self.primitives.push(Primitive::Text { + content: text.content, + bounds: Rectangle { + x: position.x, + y: position.y, + width: f32::INFINITY, + height: f32::INFINITY, + }, + color: text.color, + size: text.size, + font: text.font, + horizontal_alignment: text.horizontal_alignment, + vertical_alignment: text.vertical_alignment, + }); + } + + pub fn push_transform(&mut self) { + self.stack.push(self.transform); + } + + pub fn pop_transform(&mut self) { + self.transform = self.stack.pop().expect("Pop transform"); + } + + pub fn clip(&mut self, _frame: Self, _translation: Vector) {} + + pub fn translate(&mut self, translation: Vector) { + self.transform = + self.transform.pre_translate(translation.x, translation.y); + } + + pub fn rotate(&mut self, angle: f32) { + self.transform = self + .transform + .pre_concat(tiny_skia::Transform::from_rotate(angle.to_degrees())); + } + + pub fn scale(&mut self, scale: f32) { + self.transform = self.transform.pre_scale(scale, scale); + } + + pub fn into_primitive(self) -> Primitive { + Primitive::Clip { + bounds: Rectangle::new(Point::ORIGIN, self.size), + content: Box::new(Primitive::Group { + primitives: self.primitives, + }), + } + } +} + +fn convert_path(path: &Path) -> tiny_skia::Path { + use iced_native::widget::canvas::path::lyon_path; + + let mut builder = tiny_skia::PathBuilder::new(); + let mut last_point = Default::default(); + + for event in path.raw().iter() { + match event { + lyon_path::Event::Begin { at } => { + builder.move_to(at.x, at.y); + + last_point = at; + } + lyon_path::Event::Line { from, to } => { + if last_point != from { + builder.move_to(from.x, from.y); + } + + builder.line_to(to.x, to.y); + + last_point = to; + } + lyon_path::Event::Quadratic { from, ctrl, to } => { + if last_point != from { + builder.move_to(from.x, from.y); + } + + builder.quad_to(ctrl.x, ctrl.y, to.x, to.y); + + last_point = to; + } + lyon_path::Event::Cubic { + from, + ctrl1, + ctrl2, + to, + } => { + if last_point != from { + builder.move_to(from.x, from.y); + } + + builder + .cubic_to(ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, to.x, to.y); + + last_point = to; + } + lyon_path::Event::End { close, .. } => { + if close { + builder.close(); + } + } + } + } + + builder + .finish() + .expect("Convert lyon path to tiny_skia path") +} + +pub fn into_paint(style: Style) -> tiny_skia::Paint<'static> { + tiny_skia::Paint { + shader: match style { + Style::Solid(color) => tiny_skia::Shader::SolidColor( + tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a) + .expect("Create color"), + ), + Style::Gradient(gradient) => match gradient { + Gradient::Linear(linear) => tiny_skia::LinearGradient::new( + tiny_skia::Point { + x: linear.start.x, + y: linear.start.y, + }, + tiny_skia::Point { + x: linear.end.x, + y: linear.end.y, + }, + linear + .color_stops + .into_iter() + .map(|stop| { + tiny_skia::GradientStop::new( + stop.offset, + tiny_skia::Color::from_rgba( + stop.color.b, + stop.color.g, + stop.color.r, + stop.color.a, + ) + .expect("Create color"), + ) + }) + .collect(), + tiny_skia::SpreadMode::Pad, + tiny_skia::Transform::identity(), + ) + .expect("Create linear gradient"), + }, + }, + anti_alias: true, + ..Default::default() + } +} + +pub fn into_fill_rule(rule: fill::Rule) -> tiny_skia::FillRule { + match rule { + fill::Rule::EvenOdd => tiny_skia::FillRule::EvenOdd, + fill::Rule::NonZero => tiny_skia::FillRule::Winding, + } +} + +pub fn into_stroke(stroke: &Stroke) -> tiny_skia::Stroke { + tiny_skia::Stroke { + width: stroke.width, + line_cap: match stroke.line_cap { + stroke::LineCap::Butt => tiny_skia::LineCap::Butt, + stroke::LineCap::Square => tiny_skia::LineCap::Square, + stroke::LineCap::Round => tiny_skia::LineCap::Round, + }, + line_join: match stroke.line_join { + stroke::LineJoin::Miter => tiny_skia::LineJoin::Miter, + stroke::LineJoin::Round => tiny_skia::LineJoin::Round, + stroke::LineJoin::Bevel => tiny_skia::LineJoin::Bevel, + }, + dash: if stroke.line_dash.segments.is_empty() { + None + } else { + tiny_skia::StrokeDash::new( + stroke.line_dash.segments.into(), + stroke.line_dash.offset as f32, + ) + }, + ..Default::default() + } +} diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index 420a1ffb..e66e6412 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -4,10 +4,18 @@ mod backend; mod settings; mod text; +#[cfg(feature = "canvas")] +pub mod canvas; + +pub use iced_graphics::primitive; + pub use backend::Backend; +pub use primitive::Primitive; pub use settings::Settings; -pub use iced_graphics::{Color, Error, Font, Point, Size, Vector, Viewport}; +pub use iced_graphics::{ + Color, Error, Font, Point, Rectangle, Size, Vector, Viewport, +}; /// A [`tiny-skia`] graphics renderer for [`iced`]. /// diff --git a/tiny_skia/src/primitive.rs b/tiny_skia/src/primitive.rs new file mode 100644 index 00000000..22daaedc --- /dev/null +++ b/tiny_skia/src/primitive.rs @@ -0,0 +1,82 @@ +use crate::{Rectangle, Vector}; + +use std::sync::Arc; + +#[derive(Debug, Clone)] +pub enum Primitive { + /// A group of primitives + Group { + /// The primitives of the group + primitives: Vec, + }, + /// A clip primitive + Clip { + /// The bounds of the clip + bounds: Rectangle, + /// The content of the clip + content: Box, + }, + /// A primitive that applies a translation + Translate { + /// The translation vector + translation: Vector, + + /// The primitive to translate + content: Box, + }, + /// A cached primitive. + /// + /// This can be useful if you are implementing a widget where primitive + /// generation is expensive. + Cached { + /// The cached primitive + cache: Arc, + }, + /// A basic primitive. + Basic(iced_graphics::Primitive), +} + +impl iced_graphics::backend::Primitive for Primitive { + fn translate(self, translation: Vector) -> Self { + Self::Translate { + translation, + content: Box::new(self), + } + } + + fn clip(self, bounds: Rectangle) -> Self { + Self::Clip { + bounds, + content: Box::new(self), + } + } +} + +#[derive(Debug, Clone, Default)] +pub struct Recording(pub(crate) Vec); + +impl iced_graphics::backend::Recording for Recording { + type Primitive = Primitive; + + fn push(&mut self, primitive: Primitive) { + self.0.push(primitive); + } + + fn push_basic(&mut self, basic: iced_graphics::Primitive) { + self.0.push(Primitive::Basic(basic)); + } + + fn group(self) -> Self::Primitive { + Primitive::Group { primitives: self.0 } + } + + fn clear(&mut self) { + self.0.clear(); + } +} + +impl Recording { + pub fn primitives(&self) -> &[Primitive] { + &self.0 + } +} diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 2bd5831e..08159cd8 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -1,7 +1,6 @@ -use crate::{Backend, Color, Error, Renderer, Settings, Viewport}; +use crate::{Backend, Color, Error, Primitive, Renderer, Settings, Viewport}; use iced_graphics::window::compositor::{self, Information, SurfaceError}; -use iced_graphics::Primitive; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use std::marker::PhantomData; -- cgit From 5c0427edbb4358896412c736af2f441c12601d1b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 Mar 2023 21:41:32 +0100 Subject: Fix `Clip` primitive translation in `iced_tiny_skia` --- tiny_skia/src/backend.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index e08cede7..838426f5 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -221,7 +221,7 @@ impl Backend { pixels, Some(&rectangular_clip_mask( pixels, - *bounds * scale_factor, + (*bounds + translation) * scale_factor, )), scale_factor, translation, -- cgit From 838fd96212b14f20fe2224c4844904a8995f2db7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 Mar 2023 21:47:15 +0100 Subject: Disable `anti_alias` for `Frame::fill_rectangle` in `iced_tiny_skia` --- tiny_skia/src/canvas.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/canvas.rs b/tiny_skia/src/canvas.rs index c3b8b316..59765f59 100644 --- a/tiny_skia/src/canvas.rs +++ b/tiny_skia/src/canvas.rs @@ -44,7 +44,7 @@ impl Frame { self.primitives.push(Primitive::Fill { path, - paint: into_paint(fill.style), + paint: into_paint(fill.style, true), rule: into_fill_rule(fill.rule), transform: self.transform, }); @@ -56,7 +56,15 @@ impl Frame { size: Size, fill: impl Into, ) { - self.fill(&Path::rectangle(top_left, size), fill); + let path = convert_path(&Path::rectangle(top_left, size)); + let fill = fill.into(); + + self.primitives.push(Primitive::Fill { + path, + paint: into_paint(fill.style, false), + rule: into_fill_rule(fill.rule), + transform: self.transform, + }); } pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { @@ -66,7 +74,7 @@ impl Frame { self.primitives.push(Primitive::Stroke { path, - paint: into_paint(stroke.style), + paint: into_paint(stroke.style, true), stroke: skia_stroke, transform: self.transform, }); @@ -199,7 +207,7 @@ fn convert_path(path: &Path) -> tiny_skia::Path { .expect("Convert lyon path to tiny_skia path") } -pub fn into_paint(style: Style) -> tiny_skia::Paint<'static> { +pub fn into_paint(style: Style, anti_alias: bool) -> tiny_skia::Paint<'static> { tiny_skia::Paint { shader: match style { Style::Solid(color) => tiny_skia::Shader::SolidColor( @@ -238,7 +246,7 @@ pub fn into_paint(style: Style) -> tiny_skia::Paint<'static> { .expect("Create linear gradient"), }, }, - anti_alias: true, + anti_alias, ..Default::default() } } -- cgit From 119cf2ecd10f70471199439acb1c4f9d96a57ced Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 Mar 2023 21:48:27 +0100 Subject: Remove magic boolean in `into_paint` --- tiny_skia/src/canvas.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/canvas.rs b/tiny_skia/src/canvas.rs index 59765f59..958063d2 100644 --- a/tiny_skia/src/canvas.rs +++ b/tiny_skia/src/canvas.rs @@ -44,7 +44,7 @@ impl Frame { self.primitives.push(Primitive::Fill { path, - paint: into_paint(fill.style, true), + paint: into_paint(fill.style), rule: into_fill_rule(fill.rule), transform: self.transform, }); @@ -61,7 +61,10 @@ impl Frame { self.primitives.push(Primitive::Fill { path, - paint: into_paint(fill.style, false), + paint: tiny_skia::Paint { + anti_alias: false, + ..into_paint(fill.style) + }, rule: into_fill_rule(fill.rule), transform: self.transform, }); @@ -74,7 +77,7 @@ impl Frame { self.primitives.push(Primitive::Stroke { path, - paint: into_paint(stroke.style, true), + paint: into_paint(stroke.style), stroke: skia_stroke, transform: self.transform, }); @@ -207,7 +210,7 @@ fn convert_path(path: &Path) -> tiny_skia::Path { .expect("Convert lyon path to tiny_skia path") } -pub fn into_paint(style: Style, anti_alias: bool) -> tiny_skia::Paint<'static> { +pub fn into_paint(style: Style) -> tiny_skia::Paint<'static> { tiny_skia::Paint { shader: match style { Style::Solid(color) => tiny_skia::Shader::SolidColor( @@ -246,7 +249,7 @@ pub fn into_paint(style: Style, anti_alias: bool) -> tiny_skia::Paint<'static> { .expect("Create linear gradient"), }, }, - anti_alias, + anti_alias: true, ..Default::default() } } -- cgit From 868f79d22e2be82e98b06d66da3b4cbc6139d7c7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 2 Mar 2023 00:40:36 +0100 Subject: Reuse `ClipMask` in `iced_tiny_skia` --- tiny_skia/src/backend.rs | 41 +++++++++++++++++++++++++------------- tiny_skia/src/window/compositor.rs | 7 +++++-- 2 files changed, 32 insertions(+), 16 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 838426f5..2e4663ea 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -25,6 +25,7 @@ impl Backend { pub fn draw>( &mut self, pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: &mut tiny_skia::ClipMask, primitives: &[Primitive], viewport: &Viewport, background_color: Color, @@ -38,6 +39,7 @@ impl Backend { self.draw_primitive( primitive, pixels, + clip_mask, None, scale_factor, Vector::ZERO, @@ -63,6 +65,7 @@ impl Backend { vertical_alignment: alignment::Vertical::Top, }, pixels, + clip_mask, None, scale_factor, Vector::ZERO, @@ -76,7 +79,8 @@ impl Backend { &mut self, primitive: &Primitive, pixels: &mut tiny_skia::PixmapMut<'_>, - clip_mask: Option<&tiny_skia::ClipMask>, + clip_mask: &mut tiny_skia::ClipMask, + clip_bounds: Option, scale_factor: f32, translation: Vector, ) { @@ -95,6 +99,7 @@ impl Backend { .post_scale(scale_factor, scale_factor); let path = rounded_rectangle(*bounds, *border_radius); + let clip_mask = clip_bounds.map(|_| clip_mask as &_); pixels.fill_path( &path, @@ -151,7 +156,7 @@ impl Backend { *horizontal_alignment, *vertical_alignment, pixels, - clip_mask, + clip_bounds.map(|_| clip_mask as &_), ); } Primitive::Image { .. } => { @@ -173,7 +178,7 @@ impl Backend { transform .post_translate(translation.x, translation.y) .post_scale(scale_factor, scale_factor), - clip_mask, + clip_bounds.map(|_| clip_mask as &_), ); } Primitive::Stroke { @@ -189,7 +194,7 @@ impl Backend { transform .post_translate(translation.x, translation.y) .post_scale(scale_factor, scale_factor), - clip_mask, + clip_bounds.map(|_| clip_mask as &_), ); } Primitive::Group { primitives } => { @@ -198,6 +203,7 @@ impl Backend { primitive, pixels, clip_mask, + clip_bounds, scale_factor, translation, ); @@ -211,27 +217,37 @@ impl Backend { content, pixels, clip_mask, + clip_bounds, scale_factor, translation + *offset, ); } Primitive::Clip { bounds, content } => { + let bounds = (*bounds + translation) * scale_factor; + + adjust_clip_mask(clip_mask, pixels, bounds); + self.draw_primitive( content, pixels, - Some(&rectangular_clip_mask( - pixels, - (*bounds + translation) * scale_factor, - )), + clip_mask, + Some(bounds), scale_factor, translation, ); + + if let Some(bounds) = clip_bounds { + adjust_clip_mask(clip_mask, pixels, bounds); + } else { + clip_mask.clear(); + } } Primitive::Cache { content } => { self.draw_primitive( content, pixels, clip_mask, + clip_bounds, scale_factor, translation, ); @@ -393,12 +409,11 @@ fn arc_to( } } -fn rectangular_clip_mask( +fn adjust_clip_mask( + clip_mask: &mut tiny_skia::ClipMask, pixels: &tiny_skia::PixmapMut<'_>, bounds: Rectangle, -) -> tiny_skia::ClipMask { - let mut clip_mask = tiny_skia::ClipMask::new(); - +) { let path = { let mut builder = tiny_skia::PathBuilder::new(); builder.push_rect(bounds.x, bounds.y, bounds.width, bounds.height); @@ -415,8 +430,6 @@ fn rectangular_clip_mask( true, ) .expect("Set path of clipping area"); - - clip_mask } impl iced_graphics::Backend for Backend { diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 08159cd8..76f371e1 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -6,6 +6,7 @@ use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use std::marker::PhantomData; pub struct Compositor { + clip_mask: tiny_skia::ClipMask, _theme: PhantomData, } @@ -83,9 +84,10 @@ impl iced_graphics::window::Compositor for Compositor { } pub fn new(settings: Settings) -> (Compositor, Backend) { - // TODO + // TOD ( Compositor { + clip_mask: tiny_skia::ClipMask::new(), _theme: PhantomData, }, Backend::new(settings), @@ -93,7 +95,7 @@ pub fn new(settings: Settings) -> (Compositor, Backend) { } pub fn present>( - _compositor: &mut Compositor, + compositor: &mut Compositor, backend: &mut Backend, surface: &mut Surface, primitives: &[Primitive], @@ -110,6 +112,7 @@ pub fn present>( physical_size.height, ) .expect("Create pixel map"), + &mut compositor.clip_mask, primitives, viewport, background_color, -- cgit From d13d19ba3569560edd67f20b48f37548d10ceee9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 3 Mar 2023 04:00:44 +0100 Subject: Rename `canvas::frame` to `canvas` in `iced_wgpu` --- tiny_skia/src/backend.rs | 3 +++ 1 file changed, 3 insertions(+) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 2e4663ea..6883a953 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -256,6 +256,9 @@ impl Backend { // Not supported! // TODO: Draw a placeholder (?) / Log it (?) } + _ => { + // Not supported! + } } } } -- cgit From 6cc48b5c62bac287b8f9f1c79c1fb7486c51b18f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 3 Mar 2023 04:57:55 +0100 Subject: Move `Canvas` and `QRCode` to `iced` crate Rename `canvas` modules to `geometry` in graphics subcrates --- tiny_skia/src/backend.rs | 2 - tiny_skia/src/canvas.rs | 287 ---------------------------------------------- tiny_skia/src/geometry.rs | 287 ++++++++++++++++++++++++++++++++++++++++++++++ tiny_skia/src/lib.rs | 4 +- 4 files changed, 289 insertions(+), 291 deletions(-) delete mode 100644 tiny_skia/src/canvas.rs create mode 100644 tiny_skia/src/geometry.rs (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 6883a953..050c6c75 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -436,8 +436,6 @@ fn adjust_clip_mask( } impl iced_graphics::Backend for Backend { - type Geometry = (); - fn trim_measurements(&mut self) { self.text_pipeline.trim_measurement_cache(); } diff --git a/tiny_skia/src/canvas.rs b/tiny_skia/src/canvas.rs deleted file mode 100644 index 958063d2..00000000 --- a/tiny_skia/src/canvas.rs +++ /dev/null @@ -1,287 +0,0 @@ -use crate::{Point, Primitive, Rectangle, Size, Vector}; - -use iced_native::widget::canvas::fill::{self, Fill}; -use iced_native::widget::canvas::stroke::{self, Stroke}; -use iced_native::widget::canvas::{Path, Style, Text}; -use iced_native::Gradient; - -pub struct Frame { - size: Size, - transform: tiny_skia::Transform, - stack: Vec, - primitives: Vec, -} - -impl Frame { - pub fn new(size: Size) -> Self { - Self { - size, - transform: tiny_skia::Transform::identity(), - stack: Vec::new(), - primitives: Vec::new(), - } - } - - pub fn width(&self) -> f32 { - self.size.width - } - - pub fn height(&self) -> f32 { - self.size.height - } - - pub fn size(&self) -> Size { - self.size - } - - pub fn center(&self) -> Point { - Point::new(self.size.width / 2.0, self.size.height / 2.0) - } - - pub fn fill(&mut self, path: &Path, fill: impl Into) { - let path = convert_path(path); - let fill = fill.into(); - - self.primitives.push(Primitive::Fill { - path, - paint: into_paint(fill.style), - rule: into_fill_rule(fill.rule), - transform: self.transform, - }); - } - - pub fn fill_rectangle( - &mut self, - top_left: Point, - size: Size, - fill: impl Into, - ) { - let path = convert_path(&Path::rectangle(top_left, size)); - let fill = fill.into(); - - self.primitives.push(Primitive::Fill { - path, - paint: tiny_skia::Paint { - anti_alias: false, - ..into_paint(fill.style) - }, - rule: into_fill_rule(fill.rule), - transform: self.transform, - }); - } - - pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { - let path = convert_path(path); - let stroke = stroke.into(); - let skia_stroke = into_stroke(&stroke); - - self.primitives.push(Primitive::Stroke { - path, - paint: into_paint(stroke.style), - stroke: skia_stroke, - transform: self.transform, - }); - } - - pub fn fill_text(&mut self, text: impl Into) { - let text = text.into(); - - let position = if self.transform.is_identity() { - text.position - } else { - let mut transformed = [tiny_skia::Point { - x: text.position.x, - y: text.position.y, - }]; - - self.transform.map_points(&mut transformed); - - Point::new(transformed[0].x, transformed[0].y) - }; - - // TODO: Use vectorial text instead of primitive - self.primitives.push(Primitive::Text { - content: text.content, - bounds: Rectangle { - x: position.x, - y: position.y, - width: f32::INFINITY, - height: f32::INFINITY, - }, - color: text.color, - size: text.size, - font: text.font, - horizontal_alignment: text.horizontal_alignment, - vertical_alignment: text.vertical_alignment, - }); - } - - pub fn push_transform(&mut self) { - self.stack.push(self.transform); - } - - pub fn pop_transform(&mut self) { - self.transform = self.stack.pop().expect("Pop transform"); - } - - pub fn clip(&mut self, _frame: Self, _translation: Vector) {} - - pub fn translate(&mut self, translation: Vector) { - self.transform = - self.transform.pre_translate(translation.x, translation.y); - } - - pub fn rotate(&mut self, angle: f32) { - self.transform = self - .transform - .pre_concat(tiny_skia::Transform::from_rotate(angle.to_degrees())); - } - - pub fn scale(&mut self, scale: f32) { - self.transform = self.transform.pre_scale(scale, scale); - } - - pub fn into_primitive(self) -> Primitive { - Primitive::Clip { - bounds: Rectangle::new(Point::ORIGIN, self.size), - content: Box::new(Primitive::Group { - primitives: self.primitives, - }), - } - } -} - -fn convert_path(path: &Path) -> tiny_skia::Path { - use iced_native::widget::canvas::path::lyon_path; - - let mut builder = tiny_skia::PathBuilder::new(); - let mut last_point = Default::default(); - - for event in path.raw().iter() { - match event { - lyon_path::Event::Begin { at } => { - builder.move_to(at.x, at.y); - - last_point = at; - } - lyon_path::Event::Line { from, to } => { - if last_point != from { - builder.move_to(from.x, from.y); - } - - builder.line_to(to.x, to.y); - - last_point = to; - } - lyon_path::Event::Quadratic { from, ctrl, to } => { - if last_point != from { - builder.move_to(from.x, from.y); - } - - builder.quad_to(ctrl.x, ctrl.y, to.x, to.y); - - last_point = to; - } - lyon_path::Event::Cubic { - from, - ctrl1, - ctrl2, - to, - } => { - if last_point != from { - builder.move_to(from.x, from.y); - } - - builder - .cubic_to(ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, to.x, to.y); - - last_point = to; - } - lyon_path::Event::End { close, .. } => { - if close { - builder.close(); - } - } - } - } - - builder - .finish() - .expect("Convert lyon path to tiny_skia path") -} - -pub fn into_paint(style: Style) -> tiny_skia::Paint<'static> { - tiny_skia::Paint { - shader: match style { - Style::Solid(color) => tiny_skia::Shader::SolidColor( - tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a) - .expect("Create color"), - ), - Style::Gradient(gradient) => match gradient { - Gradient::Linear(linear) => tiny_skia::LinearGradient::new( - tiny_skia::Point { - x: linear.start.x, - y: linear.start.y, - }, - tiny_skia::Point { - x: linear.end.x, - y: linear.end.y, - }, - linear - .color_stops - .into_iter() - .map(|stop| { - tiny_skia::GradientStop::new( - stop.offset, - tiny_skia::Color::from_rgba( - stop.color.b, - stop.color.g, - stop.color.r, - stop.color.a, - ) - .expect("Create color"), - ) - }) - .collect(), - tiny_skia::SpreadMode::Pad, - tiny_skia::Transform::identity(), - ) - .expect("Create linear gradient"), - }, - }, - anti_alias: true, - ..Default::default() - } -} - -pub fn into_fill_rule(rule: fill::Rule) -> tiny_skia::FillRule { - match rule { - fill::Rule::EvenOdd => tiny_skia::FillRule::EvenOdd, - fill::Rule::NonZero => tiny_skia::FillRule::Winding, - } -} - -pub fn into_stroke(stroke: &Stroke) -> tiny_skia::Stroke { - tiny_skia::Stroke { - width: stroke.width, - line_cap: match stroke.line_cap { - stroke::LineCap::Butt => tiny_skia::LineCap::Butt, - stroke::LineCap::Square => tiny_skia::LineCap::Square, - stroke::LineCap::Round => tiny_skia::LineCap::Round, - }, - line_join: match stroke.line_join { - stroke::LineJoin::Miter => tiny_skia::LineJoin::Miter, - stroke::LineJoin::Round => tiny_skia::LineJoin::Round, - stroke::LineJoin::Bevel => tiny_skia::LineJoin::Bevel, - }, - dash: if stroke.line_dash.segments.is_empty() { - None - } else { - tiny_skia::StrokeDash::new( - stroke.line_dash.segments.into(), - stroke.line_dash.offset as f32, - ) - }, - ..Default::default() - } -} diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs new file mode 100644 index 00000000..73fc1ebd --- /dev/null +++ b/tiny_skia/src/geometry.rs @@ -0,0 +1,287 @@ +use crate::{Point, Primitive, Rectangle, Size, Vector}; + +use iced_graphics::geometry::fill::{self, Fill}; +use iced_graphics::geometry::stroke::{self, Stroke}; +use iced_graphics::geometry::{Path, Style, Text}; +use iced_graphics::Gradient; + +pub struct Frame { + size: Size, + transform: tiny_skia::Transform, + stack: Vec, + primitives: Vec, +} + +impl Frame { + pub fn new(size: Size) -> Self { + Self { + size, + transform: tiny_skia::Transform::identity(), + stack: Vec::new(), + primitives: Vec::new(), + } + } + + pub fn width(&self) -> f32 { + self.size.width + } + + pub fn height(&self) -> f32 { + self.size.height + } + + pub fn size(&self) -> Size { + self.size + } + + pub fn center(&self) -> Point { + Point::new(self.size.width / 2.0, self.size.height / 2.0) + } + + pub fn fill(&mut self, path: &Path, fill: impl Into) { + let path = convert_path(path); + let fill = fill.into(); + + self.primitives.push(Primitive::Fill { + path, + paint: into_paint(fill.style), + rule: into_fill_rule(fill.rule), + transform: self.transform, + }); + } + + pub fn fill_rectangle( + &mut self, + top_left: Point, + size: Size, + fill: impl Into, + ) { + let path = convert_path(&Path::rectangle(top_left, size)); + let fill = fill.into(); + + self.primitives.push(Primitive::Fill { + path, + paint: tiny_skia::Paint { + anti_alias: false, + ..into_paint(fill.style) + }, + rule: into_fill_rule(fill.rule), + transform: self.transform, + }); + } + + pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { + let path = convert_path(path); + let stroke = stroke.into(); + let skia_stroke = into_stroke(&stroke); + + self.primitives.push(Primitive::Stroke { + path, + paint: into_paint(stroke.style), + stroke: skia_stroke, + transform: self.transform, + }); + } + + pub fn fill_text(&mut self, text: impl Into) { + let text = text.into(); + + let position = if self.transform.is_identity() { + text.position + } else { + let mut transformed = [tiny_skia::Point { + x: text.position.x, + y: text.position.y, + }]; + + self.transform.map_points(&mut transformed); + + Point::new(transformed[0].x, transformed[0].y) + }; + + // TODO: Use vectorial text instead of primitive + self.primitives.push(Primitive::Text { + content: text.content, + bounds: Rectangle { + x: position.x, + y: position.y, + width: f32::INFINITY, + height: f32::INFINITY, + }, + color: text.color, + size: text.size, + font: text.font, + horizontal_alignment: text.horizontal_alignment, + vertical_alignment: text.vertical_alignment, + }); + } + + pub fn push_transform(&mut self) { + self.stack.push(self.transform); + } + + pub fn pop_transform(&mut self) { + self.transform = self.stack.pop().expect("Pop transform"); + } + + pub fn clip(&mut self, _frame: Self, _translation: Vector) {} + + pub fn translate(&mut self, translation: Vector) { + self.transform = + self.transform.pre_translate(translation.x, translation.y); + } + + pub fn rotate(&mut self, angle: f32) { + self.transform = self + .transform + .pre_concat(tiny_skia::Transform::from_rotate(angle.to_degrees())); + } + + pub fn scale(&mut self, scale: f32) { + self.transform = self.transform.pre_scale(scale, scale); + } + + pub fn into_primitive(self) -> Primitive { + Primitive::Clip { + bounds: Rectangle::new(Point::ORIGIN, self.size), + content: Box::new(Primitive::Group { + primitives: self.primitives, + }), + } + } +} + +fn convert_path(path: &Path) -> tiny_skia::Path { + use iced_graphics::geometry::path::lyon_path; + + let mut builder = tiny_skia::PathBuilder::new(); + let mut last_point = Default::default(); + + for event in path.raw().iter() { + match event { + lyon_path::Event::Begin { at } => { + builder.move_to(at.x, at.y); + + last_point = at; + } + lyon_path::Event::Line { from, to } => { + if last_point != from { + builder.move_to(from.x, from.y); + } + + builder.line_to(to.x, to.y); + + last_point = to; + } + lyon_path::Event::Quadratic { from, ctrl, to } => { + if last_point != from { + builder.move_to(from.x, from.y); + } + + builder.quad_to(ctrl.x, ctrl.y, to.x, to.y); + + last_point = to; + } + lyon_path::Event::Cubic { + from, + ctrl1, + ctrl2, + to, + } => { + if last_point != from { + builder.move_to(from.x, from.y); + } + + builder + .cubic_to(ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, to.x, to.y); + + last_point = to; + } + lyon_path::Event::End { close, .. } => { + if close { + builder.close(); + } + } + } + } + + builder + .finish() + .expect("Convert lyon path to tiny_skia path") +} + +pub fn into_paint(style: Style) -> tiny_skia::Paint<'static> { + tiny_skia::Paint { + shader: match style { + Style::Solid(color) => tiny_skia::Shader::SolidColor( + tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a) + .expect("Create color"), + ), + Style::Gradient(gradient) => match gradient { + Gradient::Linear(linear) => tiny_skia::LinearGradient::new( + tiny_skia::Point { + x: linear.start.x, + y: linear.start.y, + }, + tiny_skia::Point { + x: linear.end.x, + y: linear.end.y, + }, + linear + .color_stops + .into_iter() + .map(|stop| { + tiny_skia::GradientStop::new( + stop.offset, + tiny_skia::Color::from_rgba( + stop.color.b, + stop.color.g, + stop.color.r, + stop.color.a, + ) + .expect("Create color"), + ) + }) + .collect(), + tiny_skia::SpreadMode::Pad, + tiny_skia::Transform::identity(), + ) + .expect("Create linear gradient"), + }, + }, + anti_alias: true, + ..Default::default() + } +} + +pub fn into_fill_rule(rule: fill::Rule) -> tiny_skia::FillRule { + match rule { + fill::Rule::EvenOdd => tiny_skia::FillRule::EvenOdd, + fill::Rule::NonZero => tiny_skia::FillRule::Winding, + } +} + +pub fn into_stroke(stroke: &Stroke) -> tiny_skia::Stroke { + tiny_skia::Stroke { + width: stroke.width, + line_cap: match stroke.line_cap { + stroke::LineCap::Butt => tiny_skia::LineCap::Butt, + stroke::LineCap::Square => tiny_skia::LineCap::Square, + stroke::LineCap::Round => tiny_skia::LineCap::Round, + }, + line_join: match stroke.line_join { + stroke::LineJoin::Miter => tiny_skia::LineJoin::Miter, + stroke::LineJoin::Round => tiny_skia::LineJoin::Round, + stroke::LineJoin::Bevel => tiny_skia::LineJoin::Bevel, + }, + dash: if stroke.line_dash.segments.is_empty() { + None + } else { + tiny_skia::StrokeDash::new( + stroke.line_dash.segments.into(), + stroke.line_dash.offset as f32, + ) + }, + ..Default::default() + } +} diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index e66e6412..ef5c6b1d 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -4,8 +4,8 @@ mod backend; mod settings; mod text; -#[cfg(feature = "canvas")] -pub mod canvas; +#[cfg(feature = "geometry")] +pub mod geometry; pub use iced_graphics::primitive; -- cgit From 3a0d34c0240f4421737a6a08761f99d6f8140d02 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Mar 2023 05:37:11 +0100 Subject: Create `iced_widget` subcrate and re-organize the whole codebase --- tiny_skia/src/backend.rs | 18 +++++++++--------- tiny_skia/src/geometry.rs | 12 ++++++------ tiny_skia/src/lib.rs | 11 +++-------- tiny_skia/src/settings.rs | 2 +- tiny_skia/src/text.rs | 11 +++++------ tiny_skia/src/window/compositor.rs | 9 +++++---- 6 files changed, 29 insertions(+), 34 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 050c6c75..d364e36a 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -1,9 +1,9 @@ -use crate::{Color, Font, Primitive, Settings, Size, Viewport}; - -use iced_graphics::alignment; -use iced_graphics::backend; -use iced_graphics::text; -use iced_graphics::{Background, Rectangle, Vector}; +use crate::core::alignment; +use crate::core::text; +use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector}; +use crate::graphics::backend; +use crate::graphics::{Primitive, Viewport}; +use crate::Settings; use std::borrow::Cow; @@ -470,7 +470,7 @@ impl backend::Text for Backend { size: f32, font: Font, bounds: Size, - point: iced_native::Point, + point: Point, nearest_only: bool, ) -> Option { self.text_pipeline.hit_test( @@ -490,7 +490,7 @@ impl backend::Text for Backend { #[cfg(feature = "image")] impl backend::Image for Backend { - fn dimensions(&self, _handle: &iced_native::image::Handle) -> Size { + fn dimensions(&self, _handle: &crate::core::image::Handle) -> Size { // TODO Size::new(0, 0) } @@ -500,7 +500,7 @@ impl backend::Image for Backend { impl backend::Svg for Backend { fn viewport_dimensions( &self, - _handle: &iced_native::svg::Handle, + _handle: &crate::core::svg::Handle, ) -> Size { // TODO Size::new(0, 0) diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 73fc1ebd..c66621dd 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -1,9 +1,9 @@ -use crate::{Point, Primitive, Rectangle, Size, Vector}; - -use iced_graphics::geometry::fill::{self, Fill}; -use iced_graphics::geometry::stroke::{self, Stroke}; -use iced_graphics::geometry::{Path, Style, Text}; -use iced_graphics::Gradient; +use crate::core::Gradient; +use crate::core::{Point, Rectangle, Size, Vector}; +use crate::graphics::geometry::fill::{self, Fill}; +use crate::graphics::geometry::stroke::{self, Stroke}; +use crate::graphics::geometry::{Path, Style, Text}; +use crate::graphics::Primitive; pub struct Frame { size: Size, diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index ef5c6b1d..bf83e400 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -7,19 +7,14 @@ mod text; #[cfg(feature = "geometry")] pub mod geometry; -pub use iced_graphics::primitive; +pub use iced_graphics as graphics; +pub use iced_graphics::core; pub use backend::Backend; -pub use primitive::Primitive; pub use settings::Settings; -pub use iced_graphics::{ - Color, Error, Font, Point, Rectangle, Size, Vector, Viewport, -}; - /// A [`tiny-skia`] graphics renderer for [`iced`]. /// /// [`tiny-skia`]: https://github.com/RazrFalcon/tiny-skia /// [`iced`]: https://github.com/iced-rs/iced -pub type Renderer = - iced_graphics::Renderer; +pub type Renderer = iced_graphics::Renderer; diff --git a/tiny_skia/src/settings.rs b/tiny_skia/src/settings.rs index 88098345..9e4be0c4 100644 --- a/tiny_skia/src/settings.rs +++ b/tiny_skia/src/settings.rs @@ -1,4 +1,4 @@ -use crate::Font; +use crate::core::Font; /// The settings of a [`Backend`]. /// diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index da3a06bf..7a5034c2 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -1,7 +1,6 @@ -pub use iced_native::text::Hit; - -use iced_native::alignment; -use iced_native::{Color, Font, Rectangle, Size}; +use crate::core::alignment; +use crate::core::text::Hit; +use crate::core::{Color, Font, Point, Rectangle, Size}; use rustc_hash::{FxHashMap, FxHashSet}; use std::borrow::Cow; @@ -189,8 +188,8 @@ impl Pipeline { content: &str, size: f32, font: iced_native::Font, - bounds: iced_native::Size, - point: iced_native::Point, + bounds: Size, + point: Point, _nearest_only: bool, ) -> Option { self.system.as_ref().unwrap().with(|fields| { diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 76f371e1..cea1cabf 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -1,6 +1,7 @@ -use crate::{Backend, Color, Error, Primitive, Renderer, Settings, Viewport}; - -use iced_graphics::window::compositor::{self, Information, SurfaceError}; +use crate::core::Color; +use crate::graphics::compositor::{self, Information, SurfaceError}; +use crate::graphics::{Error, Primitive, Viewport}; +use crate::{Backend, Renderer, Settings}; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use std::marker::PhantomData; @@ -15,7 +16,7 @@ pub struct Surface { buffer: Vec, } -impl iced_graphics::window::Compositor for Compositor { +impl crate::graphics::Compositor for Compositor { type Settings = Settings; type Renderer = Renderer; type Surface = Surface; -- cgit From 99e0a71504456976ba88040f5d1d3bbc347694ea Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 5 Mar 2023 06:35:20 +0100 Subject: Rename `iced_native` to `iced_runtime` --- tiny_skia/src/text.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 7a5034c2..c4edadb3 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -187,7 +187,7 @@ impl Pipeline { &self, content: &str, size: f32, - font: iced_native::Font, + font: Font, bounds: Size, point: Point, _nearest_only: bool, -- cgit From bb49e17cabd45f3a21af98b4c5ecdddd507fd427 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 Mar 2023 05:06:26 +0100 Subject: Implement `raster` pipeline in `iced_tiny_skia` --- tiny_skia/src/backend.rs | 28 ++++++++++--- tiny_skia/src/lib.rs | 3 ++ tiny_skia/src/raster.rs | 107 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 tiny_skia/src/raster.rs (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index d364e36a..d894ab95 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -11,6 +11,9 @@ pub struct Backend { default_font: Font, default_text_size: f32, text_pipeline: crate::text::Pipeline, + + #[cfg(feature = "image")] + raster_pipeline: crate::raster::Pipeline, } impl Backend { @@ -19,6 +22,9 @@ impl Backend { default_font: settings.default_font, default_text_size: settings.default_text_size, text_pipeline: crate::text::Pipeline::new(), + + #[cfg(feature = "image")] + raster_pipeline: crate::raster::Pipeline::new(), } } @@ -159,8 +165,21 @@ impl Backend { clip_bounds.map(|_| clip_mask as &_), ); } - Primitive::Image { .. } => { - // TODO + #[cfg(feature = "image")] + Primitive::Image { handle, bounds } => { + let transform = tiny_skia::Transform::from_translate( + translation.x, + translation.y, + ) + .post_scale(scale_factor, scale_factor); + + self.raster_pipeline.draw( + handle, + *bounds, + pixels, + transform, + clip_bounds.map(|_| clip_mask as &_), + ); } Primitive::Svg { .. } => { // TODO @@ -490,9 +509,8 @@ impl backend::Text for Backend { #[cfg(feature = "image")] impl backend::Image for Backend { - fn dimensions(&self, _handle: &crate::core::image::Handle) -> Size { - // TODO - Size::new(0, 0) + fn dimensions(&self, handle: &crate::core::image::Handle) -> Size { + self.raster_pipeline.dimensions(handle) } } diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index bf83e400..d03bdcc2 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -4,6 +4,9 @@ mod backend; mod settings; mod text; +#[cfg(feature = "image")] +mod raster; + #[cfg(feature = "geometry")] pub mod geometry; diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs new file mode 100644 index 00000000..e57f0e50 --- /dev/null +++ b/tiny_skia/src/raster.rs @@ -0,0 +1,107 @@ +use crate::core::image as raster; +use crate::core::{Rectangle, Size}; +use crate::graphics; + +use rustc_hash::{FxHashMap, FxHashSet}; +use std::cell::RefCell; +use std::collections::hash_map; + +pub struct Pipeline { + cache: RefCell, +} + +impl Pipeline { + pub fn new() -> Self { + Self { + cache: RefCell::new(Cache::default()), + } + } + + pub fn dimensions(&self, handle: &raster::Handle) -> Size { + if let Some(image) = self.cache.borrow_mut().allocate(handle) { + Size::new(image.width(), image.height()) + } else { + Size::new(0, 0) + } + } + + pub fn draw( + &mut self, + handle: &raster::Handle, + bounds: Rectangle, + pixels: &mut tiny_skia::PixmapMut<'_>, + transform: tiny_skia::Transform, + clip_mask: Option<&tiny_skia::ClipMask>, + ) { + if let Some(image) = self.cache.borrow_mut().allocate(handle) { + let width_scale = bounds.width / image.width() as f32; + let height_scale = bounds.height / image.height() as f32; + + let transform = transform.pre_scale(width_scale, height_scale); + + pixels.draw_pixmap( + (bounds.x / width_scale) as i32, + (bounds.y / height_scale) as i32, + image, + &tiny_skia::PixmapPaint { + quality: tiny_skia::FilterQuality::Bilinear, + ..Default::default() + }, + transform, + clip_mask, + ); + } + } +} + +#[derive(Default)] +struct Cache { + entries: FxHashMap>, + hits: FxHashSet, +} + +impl Cache { + pub fn allocate( + &mut self, + handle: &raster::Handle, + ) -> Option> { + let id = handle.id(); + + if let hash_map::Entry::Vacant(entry) = self.entries.entry(id) { + let image = graphics::image::load(handle).ok()?.into_rgba8(); + + let mut buffer = + vec![0u32; image.width() as usize * image.height() as usize]; + + for (i, pixel) in image.pixels().enumerate() { + let [r, g, b, a] = pixel.0; + + buffer[i] = tiny_skia::ColorU8::from_rgba(b, g, r, a) + .premultiply() + .get(); + } + + entry.insert(Some(Entry { + width: image.width(), + height: image.height(), + pixels: buffer, + })); + } + + self.hits.insert(id); + self.entries.get(&id).unwrap().as_ref().map(|entry| { + tiny_skia::PixmapRef::from_bytes( + bytemuck::cast_slice(&entry.pixels), + entry.width, + entry.height, + ) + .expect("Build pixmap from image bytes") + }) + } +} + +struct Entry { + width: u32, + height: u32, + pixels: Vec, +} -- cgit 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/backend.rs | 30 ++++++++-- tiny_skia/src/lib.rs | 3 + tiny_skia/src/text.rs | 2 +- tiny_skia/src/vector.rs | 149 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 177 insertions(+), 7 deletions(-) create mode 100644 tiny_skia/src/vector.rs (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index d894ab95..3c2a97b9 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -14,6 +14,9 @@ pub struct Backend { #[cfg(feature = "image")] raster_pipeline: crate::raster::Pipeline, + + #[cfg(feature = "svg")] + vector_pipeline: crate::vector::Pipeline, } impl Backend { @@ -25,6 +28,9 @@ impl Backend { #[cfg(feature = "image")] 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( @@ -181,8 +190,18 @@ impl Backend { clip_bounds.map(|_| clip_mask as &_), ); } - Primitive::Svg { .. } => { - // TODO + #[cfg(feature = "svg")] + 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 { path, @@ -518,9 +537,8 @@ impl backend::Image for Backend { impl backend::Svg for Backend { fn viewport_dimensions( &self, - _handle: &crate::core::svg::Handle, + handle: &crate::core::svg::Handle, ) -> Size { - // TODO - Size::new(0, 0) + self.vector_pipeline.viewport_dimensions(handle) } } diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index d03bdcc2..83baef1c 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -7,6 +7,9 @@ mod text; #[cfg(feature = "image")] mod raster; +#[cfg(feature = "svg")] +mod vector; + #[cfg(feature = "geometry")] pub mod geometry; diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index c4edadb3..bfe5da9d 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -143,7 +143,7 @@ impl Pipeline { }); } - pub fn end_frame(&mut self) { + pub fn trim_cache(&mut self) { self.system .as_mut() .unwrap() 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 a8d55ceb829377725b4e7632702894fed6867eda Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 Mar 2023 06:15:05 +0100 Subject: Trim `raster` cache in `iced_tiny_skia` --- tiny_skia/src/backend.rs | 3 +++ tiny_skia/src/raster.rs | 9 +++++++++ 2 files changed, 12 insertions(+) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 3c2a97b9..b3c7d2bc 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -86,6 +86,9 @@ impl Backend { self.text_pipeline.trim_cache(); + #[cfg(feature = "image")] + self.raster_pipeline.trim_cache(); + #[cfg(feature = "svg")] self.vector_pipeline.trim_cache(); } diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs index e57f0e50..2fd73f8c 100644 --- a/tiny_skia/src/raster.rs +++ b/tiny_skia/src/raster.rs @@ -52,6 +52,10 @@ impl Pipeline { ); } } + + pub fn trim_cache(&mut self) { + self.cache.borrow_mut().trim(); + } } #[derive(Default)] @@ -98,6 +102,11 @@ impl Cache { .expect("Build pixmap from image bytes") }) } + + fn trim(&mut self) { + self.entries.retain(|key, _| self.hits.contains(key)); + self.hits.clear(); + } } struct Entry { -- cgit From 0850f52d8c06bd4c5ee80609758197a093939d2d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 Mar 2023 06:23:09 +0100 Subject: Use `ceil` to avoid cut text in `iced_tiny_skia` This won't be necessary once we reuse the buffers from layouting by leveraging layout linearity. --- tiny_skia/src/text.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index bfe5da9d..f2935efa 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -84,7 +84,12 @@ impl Pipeline { ) { self.system.as_mut().unwrap().with_mut(|fields| { let key = Key { - bounds: bounds.size(), + bounds: { + let size = bounds.size(); + + // TODO: Reuse buffers from layouting + Size::new(size.width.ceil(), size.height.ceil()) + }, content, font, size, -- 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') 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/backend.rs | 3 ++- tiny_skia/src/vector.rs | 66 +++++++++++++++++++++++++++++++++++------------- 2 files changed, 50 insertions(+), 19 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index b3c7d2bc..ba063f4e 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -197,10 +197,11 @@ impl Backend { Primitive::Svg { handle, bounds, - color: _, // TODO: Implement color filter + color, } => { self.vector_pipeline.draw( handle, + *color, (*bounds + translation) * scale_factor, pixels, clip_bounds.map(|_| clip_mask as &_), 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