From 3645d34d6a1ba1247238e830e9eefd52d9e5b986 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 21 Mar 2024 22:27:17 +0100 Subject: Implement composable, type-safe renderer fallback --- tiny_skia/src/backend.rs | 9 +++++++ tiny_skia/src/geometry.rs | 60 ++++++++++++++++++++++++++++------------------ tiny_skia/src/primitive.rs | 10 +++++++- 3 files changed, 55 insertions(+), 24 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index b6487b38..f6bb1c86 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -1018,3 +1018,12 @@ impl backend::Svg for Backend { self.vector_pipeline.viewport_dimensions(handle) } } + +#[cfg(feature = "geometry")] +impl crate::graphics::geometry::Backend for Backend { + type Frame = crate::geometry::Frame; + + fn new_frame(&self, size: Size) -> Self::Frame { + crate::geometry::Frame::new(size) + } +} diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 16787f89..6b1888d0 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -4,7 +4,7 @@ use crate::core::{ }; use crate::graphics::geometry::fill::{self, Fill}; use crate::graphics::geometry::stroke::{self, Stroke}; -use crate::graphics::geometry::{Path, Style, Text}; +use crate::graphics::geometry::{self, Path, Style, Text}; use crate::graphics::Gradient; use crate::primitive::{self, Primitive}; @@ -25,23 +25,36 @@ impl Frame { } } - pub fn width(&self) -> f32 { + pub fn into_primitive(self) -> Primitive { + Primitive::Clip { + bounds: Rectangle::new(Point::ORIGIN, self.size), + content: Box::new(Primitive::Group { + primitives: self.primitives, + }), + } + } +} + +impl geometry::Frame for Frame { + type Geometry = Primitive; + + fn width(&self) -> f32 { self.size.width } - pub fn height(&self) -> f32 { + fn height(&self) -> f32 { self.size.height } - pub fn size(&self) -> Size { + fn size(&self) -> Size { self.size } - pub fn center(&self) -> Point { + 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) { + fn fill(&mut self, path: &Path, fill: impl Into) { let Some(path) = convert_path(path).and_then(|path| path.transform(self.transform)) else { @@ -61,7 +74,7 @@ impl Frame { })); } - pub fn fill_rectangle( + fn fill_rectangle( &mut self, top_left: Point, size: Size, @@ -89,7 +102,7 @@ impl Frame { })); } - pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { + fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { let Some(path) = convert_path(path).and_then(|path| path.transform(self.transform)) else { @@ -110,7 +123,7 @@ impl Frame { })); } - pub fn fill_text(&mut self, text: impl Into) { + fn fill_text(&mut self, text: impl Into) { let text = text.into(); let (scale_x, scale_y) = self.transform.get_scale(); @@ -174,51 +187,52 @@ impl Frame { } } - pub fn push_transform(&mut self) { + fn push_transform(&mut self) { self.stack.push(self.transform); } - pub fn pop_transform(&mut self) { + fn pop_transform(&mut self) { self.transform = self.stack.pop().expect("Pop transform"); } - pub fn clip(&mut self, frame: Self, at: Point) { + fn draft(&mut self, size: Size) -> Self { + Self::new(size) + } + + fn paste(&mut self, frame: Self, at: Point) { self.primitives.push(Primitive::Transform { transformation: Transformation::translate(at.x, at.y), content: Box::new(frame.into_primitive()), }); } - pub fn translate(&mut self, translation: Vector) { + fn translate(&mut self, translation: Vector) { self.transform = self.transform.pre_translate(translation.x, translation.y); } - pub fn rotate(&mut self, angle: impl Into) { + fn rotate(&mut self, angle: impl Into) { self.transform = self.transform.pre_concat( tiny_skia::Transform::from_rotate(angle.into().0.to_degrees()), ); } - pub fn scale(&mut self, scale: impl Into) { + fn scale(&mut self, scale: impl Into) { let scale = scale.into(); self.scale_nonuniform(Vector { x: scale, y: scale }); } - pub fn scale_nonuniform(&mut self, scale: impl Into) { + fn scale_nonuniform(&mut self, scale: impl Into) { let scale = scale.into(); self.transform = self.transform.pre_scale(scale.x, scale.y); } +} - pub fn into_primitive(self) -> Primitive { - Primitive::Clip { - bounds: Rectangle::new(Point::ORIGIN, self.size), - content: Box::new(Primitive::Group { - primitives: self.primitives, - }), - } +impl From for Primitive { + fn from(frame: Frame) -> Self { + frame.into_primitive() } } diff --git a/tiny_skia/src/primitive.rs b/tiny_skia/src/primitive.rs index 7718d542..b7c428e4 100644 --- a/tiny_skia/src/primitive.rs +++ b/tiny_skia/src/primitive.rs @@ -1,5 +1,5 @@ use crate::core::Rectangle; -use crate::graphics::Damage; +use crate::graphics::{Damage, Mesh}; pub type Primitive = crate::graphics::Primitive; @@ -42,3 +42,11 @@ impl Damage for Custom { } } } + +impl TryFrom for Custom { + type Error = &'static str; + + fn try_from(_mesh: Mesh) -> Result { + Err("unsupported") + } +} -- cgit From 53a183fe0d6aed460fbb8155ac9541757277aab3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 01:35:14 +0100 Subject: Restore `canvas::Frame` API --- tiny_skia/src/geometry.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 6b1888d0..76482e12 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -35,7 +35,7 @@ impl Frame { } } -impl geometry::Frame for Frame { +impl geometry::frame::Backend for Frame { type Geometry = Primitive; fn width(&self) -> f32 { @@ -228,11 +228,9 @@ impl geometry::Frame for Frame { self.transform = self.transform.pre_scale(scale.x, scale.y); } -} -impl From for Primitive { - fn from(frame: Frame) -> Self { - frame.into_primitive() + fn into_geometry(self) -> Self::Geometry { + self.into_primitive() } } -- cgit From 1f13a91361258a1607c71f4840a26a6437f88612 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 05:27:31 +0100 Subject: Make `iced_tiny_skia` optional with a `tiny-skia` feature --- 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 f6bb1c86..b0dd4759 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -5,6 +5,7 @@ use crate::graphics::backend; use crate::graphics::text; use crate::graphics::{Damage, Viewport}; use crate::primitive::{self, Primitive}; +use crate::window; use std::borrow::Cow; @@ -990,6 +991,8 @@ fn rounded_box_sdf( } impl iced_graphics::Backend for Backend { + type Compositor = window::Compositor; + type Primitive = primitive::Custom; } -- cgit From 4f2f40c68b4647f281d34034beb159a41422aa06 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 05:41:15 +0100 Subject: Fix standalone compilation of `iced_widget` crate --- tiny_skia/src/backend.rs | 1 - 1 file changed, 1 deletion(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index b0dd4759..6d4e6cda 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -992,7 +992,6 @@ fn rounded_box_sdf( impl iced_graphics::Backend for Backend { type Compositor = window::Compositor; - type Primitive = primitive::Custom; } -- cgit From 5137d655e6bbd29581fc1469d0385515113f2999 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 07:09:51 +0100 Subject: Allow custom renderers in `Program` and `Application` --- tiny_skia/src/settings.rs | 10 ++++++++++ tiny_skia/src/window/compositor.rs | 7 +++---- 2 files changed, 13 insertions(+), 4 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/settings.rs b/tiny_skia/src/settings.rs index ec27b218..01d015b4 100644 --- a/tiny_skia/src/settings.rs +++ b/tiny_skia/src/settings.rs @@ -1,4 +1,5 @@ use crate::core::{Font, Pixels}; +use crate::graphics; /// The settings of a [`Backend`]. /// @@ -22,3 +23,12 @@ impl Default for Settings { } } } + +impl From for Settings { + fn from(settings: graphics::Settings) -> Self { + Self { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + } + } +} diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index a98825f1..0c08097b 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -1,7 +1,7 @@ use crate::core::{Color, Rectangle, Size}; use crate::graphics::compositor::{self, Information}; use crate::graphics::damage; -use crate::graphics::{Error, Viewport}; +use crate::graphics::{self, Error, Viewport}; use crate::{Backend, Primitive, Renderer, Settings}; use std::collections::VecDeque; @@ -25,15 +25,14 @@ pub struct Surface { } impl crate::graphics::Compositor for Compositor { - type Settings = Settings; type Renderer = Renderer; type Surface = Surface; fn new( - settings: Self::Settings, + settings: graphics::Settings, compatible_window: W, ) -> impl Future> { - future::ready(Ok(new(settings, compatible_window))) + future::ready(Ok(new(settings.into(), compatible_window))) } fn create_renderer(&self) -> Self::Renderer { -- cgit From 441e9237cd1c9c9b61d9b144b5b4dafa236ace28 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 19:35:19 +0100 Subject: Rename `compositor::Renderer` to `Default` --- tiny_skia/src/backend.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 6d4e6cda..8c8781e3 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -990,9 +990,9 @@ fn rounded_box_sdf( (x.powf(2.0) + y.powf(2.0)).sqrt() - radius } -impl iced_graphics::Backend for Backend { - type Compositor = window::Compositor; +impl backend::Backend for Backend { type Primitive = primitive::Custom; + type Compositor = window::Compositor; } impl backend::Text for Backend { -- cgit From 4f5b63f1f4cd7d3ab72289c697f4abc767114eca Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 24 Mar 2024 08:04:28 +0100 Subject: Reintroduce backend selection through `ICED_BACKEND` env var --- tiny_skia/src/window/compositor.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 0c08097b..25c57dc1 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -1,11 +1,11 @@ use crate::core::{Color, Rectangle, Size}; use crate::graphics::compositor::{self, Information}; use crate::graphics::damage; -use crate::graphics::{self, Error, Viewport}; +use crate::graphics::error::{self, Error}; +use crate::graphics::{self, Viewport}; use crate::{Backend, Primitive, Renderer, Settings}; use std::collections::VecDeque; -use std::future::{self, Future}; use std::num::NonZeroU32; pub struct Compositor { @@ -28,11 +28,22 @@ impl crate::graphics::Compositor for Compositor { type Renderer = Renderer; type Surface = Surface; - fn new( + async fn with_backend( settings: graphics::Settings, compatible_window: W, - ) -> impl Future> { - future::ready(Ok(new(settings.into(), compatible_window))) + backend: Option<&str>, + ) -> Result { + match backend { + None | Some("tiny-skia") | Some("tiny_skia") => { + Ok(new(settings.into(), compatible_window)) + } + Some(backend) => Err(Error::GraphicsAdapterNotFound { + backend: "tiny-skia", + reason: error::Reason::DidNotMatch { + preferred_backend: backend.to_owned(), + }, + }), + } } fn create_renderer(&self) -> Self::Renderer { -- cgit From 39f57a258ee315f51a968a9b4fcfc0ec0ea56ee0 Mon Sep 17 00:00:00 2001 From: Daniel Yoon <101683475+Koranir@users.noreply.github.com> Date: Tue, 26 Mar 2024 13:02:04 +1100 Subject: Make tiny-skia equivalent --- tiny_skia/src/backend.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 8c8781e3..0c913c02 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -233,7 +233,8 @@ impl Backend { ), size, &radii, - ); + ) + .max(0.0); let shadow_alpha = 1.0 - smoothstep( -shadow.blur_radius * scale_factor, -- cgit From 6d3e1d835e1688fbc58622a03a784ed25ed3f0e1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 5 Apr 2024 23:59:21 +0200 Subject: Decouple caching from layering and simplify everything --- tiny_skia/src/geometry.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 76482e12..f1d11dce 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -195,8 +195,8 @@ impl geometry::frame::Backend for Frame { self.transform = self.stack.pop().expect("Pop transform"); } - fn draft(&mut self, size: Size) -> Self { - Self::new(size) + fn draft(&mut self, clip_bounds: Rectangle) -> Self { + Self::new(clip_bounds.size()) } fn paste(&mut self, frame: Self, at: Point) { -- cgit From 5cd98f069dea8720bca7748d6c12fa410cbe79b5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 7 Apr 2024 12:42:12 +0200 Subject: Use built-in `[lints]` table in `Cargo.toml` --- tiny_skia/src/backend.rs | 1 + tiny_skia/src/geometry.rs | 1 + tiny_skia/src/lib.rs | 3 +-- tiny_skia/src/raster.rs | 4 +++- tiny_skia/src/text.rs | 2 +- tiny_skia/src/vector.rs | 11 +++++++++++ tiny_skia/src/window/compositor.rs | 2 ++ 7 files changed, 20 insertions(+), 4 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 0c913c02..d0f28876 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -9,6 +9,7 @@ use crate::window; use std::borrow::Cow; +#[derive(Debug)] pub struct Backend { text_pipeline: crate::text::Pipeline, diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 76482e12..9bc3664d 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -8,6 +8,7 @@ use crate::graphics::geometry::{self, Path, Style, Text}; use crate::graphics::Gradient; use crate::primitive::{self, Primitive}; +#[derive(Debug)] pub struct Frame { size: Size, transform: tiny_skia::Transform, diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index e7294f9b..d1f68daa 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -1,5 +1,4 @@ -#![forbid(rust_2018_idioms)] -#![deny(unsafe_code, unused_results, rustdoc::broken_intra_doc_links)] +#![allow(missing_docs)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] pub mod window; diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs index 5f17ae60..176b0da9 100644 --- a/tiny_skia/src/raster.rs +++ b/tiny_skia/src/raster.rs @@ -6,6 +6,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use std::cell::RefCell; use std::collections::hash_map; +#[derive(Debug)] pub struct Pipeline { cache: RefCell, } @@ -68,7 +69,7 @@ impl Pipeline { } } -#[derive(Default)] +#[derive(Debug, Default)] struct Cache { entries: FxHashMap>, hits: FxHashSet, @@ -119,6 +120,7 @@ impl Cache { } } +#[derive(Debug)] struct Entry { width: u32, height: u32, diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index d28cc483..66ee88da 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -13,7 +13,7 @@ use std::borrow::Cow; use std::cell::RefCell; use std::collections::hash_map; -#[allow(missing_debug_implementations)] +#[derive(Debug)] pub struct Pipeline { glyph_cache: GlyphCache, cache: RefCell, diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs index fd1ab3de..5150cffe 100644 --- a/tiny_skia/src/vector.rs +++ b/tiny_skia/src/vector.rs @@ -9,6 +9,7 @@ use std::cell::RefCell; use std::collections::hash_map; use std::fs; +#[derive(Debug)] pub struct Pipeline { cache: RefCell, } @@ -203,3 +204,13 @@ impl Cache { self.raster_hits.clear(); } } + +impl std::fmt::Debug for Cache { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Cache") + .field("tree_hits", &self.tree_hits) + .field("rasters", &self.rasters) + .field("raster_hits", &self.raster_hits) + .finish_non_exhaustive() + } +} diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 25c57dc1..2350adb9 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -8,11 +8,13 @@ use crate::{Backend, Primitive, Renderer, Settings}; use std::collections::VecDeque; use std::num::NonZeroU32; +#[allow(missing_debug_implementations)] pub struct Compositor { context: softbuffer::Context>, settings: Settings, } +#[allow(missing_debug_implementations)] pub struct Surface { window: softbuffer::Surface< Box, -- cgit From 6ad5bb3597f640ac329801adf735d633bf0a512f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 9 Apr 2024 22:25:16 +0200 Subject: Port `iced_tiny_skia` to new layering architecture --- tiny_skia/src/backend.rs | 1033 ------------------------------------ tiny_skia/src/engine.rs | 831 +++++++++++++++++++++++++++++ tiny_skia/src/geometry.rs | 143 +++-- tiny_skia/src/layer.rs | 243 +++++++++ tiny_skia/src/lib.rs | 383 ++++++++++++- tiny_skia/src/primitive.rs | 33 +- tiny_skia/src/settings.rs | 4 +- tiny_skia/src/text.rs | 29 +- tiny_skia/src/window/compositor.rs | 72 +-- 9 files changed, 1584 insertions(+), 1187 deletions(-) delete mode 100644 tiny_skia/src/backend.rs create mode 100644 tiny_skia/src/engine.rs create mode 100644 tiny_skia/src/layer.rs (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs deleted file mode 100644 index d0f28876..00000000 --- a/tiny_skia/src/backend.rs +++ /dev/null @@ -1,1033 +0,0 @@ -use crate::core::{ - Background, Color, Gradient, Rectangle, Size, Transformation, Vector, -}; -use crate::graphics::backend; -use crate::graphics::text; -use crate::graphics::{Damage, Viewport}; -use crate::primitive::{self, Primitive}; -use crate::window; - -use std::borrow::Cow; - -#[derive(Debug)] -pub struct Backend { - text_pipeline: crate::text::Pipeline, - - #[cfg(feature = "image")] - raster_pipeline: crate::raster::Pipeline, - - #[cfg(feature = "svg")] - vector_pipeline: crate::vector::Pipeline, -} - -impl Backend { - pub fn new() -> Self { - Self { - text_pipeline: crate::text::Pipeline::new(), - - #[cfg(feature = "image")] - raster_pipeline: crate::raster::Pipeline::new(), - - #[cfg(feature = "svg")] - vector_pipeline: crate::vector::Pipeline::new(), - } - } - - pub fn draw>( - &mut self, - pixels: &mut tiny_skia::PixmapMut<'_>, - clip_mask: &mut tiny_skia::Mask, - primitives: &[Primitive], - viewport: &Viewport, - damage: &[Rectangle], - background_color: Color, - overlay: &[T], - ) { - let physical_size = viewport.physical_size(); - let scale_factor = viewport.scale_factor() as f32; - - if !overlay.is_empty() { - let path = tiny_skia::PathBuilder::from_rect( - tiny_skia::Rect::from_xywh( - 0.0, - 0.0, - physical_size.width as f32, - physical_size.height as f32, - ) - .expect("Create damage rectangle"), - ); - - pixels.fill_path( - &path, - &tiny_skia::Paint { - shader: tiny_skia::Shader::SolidColor(into_color(Color { - a: 0.1, - ..background_color - })), - anti_alias: false, - ..Default::default() - }, - tiny_skia::FillRule::default(), - tiny_skia::Transform::identity(), - None, - ); - } - - for ®ion in damage { - let path = tiny_skia::PathBuilder::from_rect( - tiny_skia::Rect::from_xywh( - region.x, - region.y, - region.width, - region.height, - ) - .expect("Create damage rectangle"), - ); - - pixels.fill_path( - &path, - &tiny_skia::Paint { - shader: tiny_skia::Shader::SolidColor(into_color( - background_color, - )), - anti_alias: false, - blend_mode: tiny_skia::BlendMode::Source, - ..Default::default() - }, - tiny_skia::FillRule::default(), - tiny_skia::Transform::identity(), - None, - ); - - adjust_clip_mask(clip_mask, region); - - for primitive in primitives { - self.draw_primitive( - primitive, - pixels, - clip_mask, - region, - scale_factor, - Transformation::IDENTITY, - ); - } - - if !overlay.is_empty() { - pixels.stroke_path( - &path, - &tiny_skia::Paint { - shader: tiny_skia::Shader::SolidColor(into_color( - Color::from_rgb(1.0, 0.0, 0.0), - )), - anti_alias: false, - ..tiny_skia::Paint::default() - }, - &tiny_skia::Stroke { - width: 1.0, - ..tiny_skia::Stroke::default() - }, - tiny_skia::Transform::identity(), - None, - ); - } - } - - self.text_pipeline.trim_cache(); - - #[cfg(feature = "image")] - self.raster_pipeline.trim_cache(); - - #[cfg(feature = "svg")] - self.vector_pipeline.trim_cache(); - } - - fn draw_primitive( - &mut self, - primitive: &Primitive, - pixels: &mut tiny_skia::PixmapMut<'_>, - clip_mask: &mut tiny_skia::Mask, - clip_bounds: Rectangle, - scale_factor: f32, - transformation: Transformation, - ) { - match primitive { - Primitive::Quad { - bounds, - background, - border, - shadow, - } => { - debug_assert!( - bounds.width.is_normal(), - "Quad with non-normal width!" - ); - debug_assert!( - bounds.height.is_normal(), - "Quad with non-normal height!" - ); - - let physical_bounds = (*bounds * transformation) * scale_factor; - - if !clip_bounds.intersects(&physical_bounds) { - return; - } - - let clip_mask = (!physical_bounds.is_within(&clip_bounds)) - .then_some(clip_mask as &_); - - let transform = into_transform(transformation) - .post_scale(scale_factor, scale_factor); - - // Make sure the border radius is not larger than the bounds - let border_width = border - .width - .min(bounds.width / 2.0) - .min(bounds.height / 2.0); - - let mut fill_border_radius = <[f32; 4]>::from(border.radius); - for radius in &mut fill_border_radius { - *radius = (*radius) - .min(bounds.width / 2.0) - .min(bounds.height / 2.0); - } - let path = rounded_rectangle(*bounds, fill_border_radius); - - if shadow.color.a > 0.0 { - let shadow_bounds = (Rectangle { - x: bounds.x + shadow.offset.x - shadow.blur_radius, - y: bounds.y + shadow.offset.y - shadow.blur_radius, - width: bounds.width + shadow.blur_radius * 2.0, - height: bounds.height + shadow.blur_radius * 2.0, - } * transformation) - * scale_factor; - - let radii = fill_border_radius - .into_iter() - .map(|radius| radius * scale_factor) - .collect::>(); - let (x, y, width, height) = ( - shadow_bounds.x as u32, - shadow_bounds.y as u32, - shadow_bounds.width as u32, - shadow_bounds.height as u32, - ); - let half_width = physical_bounds.width / 2.0; - let half_height = physical_bounds.height / 2.0; - - let colors = (y..y + height) - .flat_map(|y| { - (x..x + width).map(move |x| (x as f32, y as f32)) - }) - .filter_map(|(x, y)| { - tiny_skia::Size::from_wh(half_width, half_height) - .map(|size| { - let shadow_distance = rounded_box_sdf( - Vector::new( - x - physical_bounds.position().x - - (shadow.offset.x - * scale_factor) - - half_width, - y - physical_bounds.position().y - - (shadow.offset.y - * scale_factor) - - half_height, - ), - size, - &radii, - ) - .max(0.0); - let shadow_alpha = 1.0 - - smoothstep( - -shadow.blur_radius * scale_factor, - shadow.blur_radius * scale_factor, - shadow_distance, - ); - - let mut color = into_color(shadow.color); - color.apply_opacity(shadow_alpha); - - color.to_color_u8().premultiply() - }) - }) - .collect(); - - if let Some(pixmap) = tiny_skia::IntSize::from_wh( - width, height, - ) - .and_then(|size| { - tiny_skia::Pixmap::from_vec( - bytemuck::cast_vec(colors), - size, - ) - }) { - pixels.draw_pixmap( - x as i32, - y as i32, - pixmap.as_ref(), - &tiny_skia::PixmapPaint::default(), - tiny_skia::Transform::default(), - None, - ); - } - } - - pixels.fill_path( - &path, - &tiny_skia::Paint { - shader: match background { - Background::Color(color) => { - tiny_skia::Shader::SolidColor(into_color( - *color, - )) - } - Background::Gradient(Gradient::Linear(linear)) => { - let (start, end) = - linear.angle.to_distance(bounds); - - let stops: Vec = - linear - .stops - .into_iter() - .flatten() - .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::LinearGradient::new( - tiny_skia::Point { - x: start.x, - y: start.y, - }, - tiny_skia::Point { x: end.x, y: end.y }, - if stops.is_empty() { - vec![tiny_skia::GradientStop::new( - 0.0, - tiny_skia::Color::BLACK, - )] - } else { - stops - }, - tiny_skia::SpreadMode::Pad, - tiny_skia::Transform::identity(), - ) - .expect("Create linear gradient") - } - }, - anti_alias: true, - ..tiny_skia::Paint::default() - }, - tiny_skia::FillRule::EvenOdd, - transform, - clip_mask, - ); - - if border_width > 0.0 { - // Border path is offset by half the border width - let border_bounds = Rectangle { - x: bounds.x + border_width / 2.0, - y: bounds.y + border_width / 2.0, - width: bounds.width - border_width, - height: bounds.height - border_width, - }; - - // Make sure the border radius is correct - let mut border_radius = <[f32; 4]>::from(border.radius); - let mut is_simple_border = true; - - for radius in &mut border_radius { - *radius = if *radius == 0.0 { - // Path should handle this fine - 0.0 - } else if *radius > border_width / 2.0 { - *radius - border_width / 2.0 - } else { - is_simple_border = false; - 0.0 - } - .min(border_bounds.width / 2.0) - .min(border_bounds.height / 2.0); - } - - // Stroking a path works well in this case - if is_simple_border { - let border_path = - rounded_rectangle(border_bounds, border_radius); - - pixels.stroke_path( - &border_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, - ); - } else { - // Draw corners that have too small border radii as having no border radius, - // but mask them with the rounded rectangle with the correct border radius. - let mut temp_pixmap = tiny_skia::Pixmap::new( - bounds.width as u32, - bounds.height as u32, - ) - .unwrap(); - - let mut quad_mask = tiny_skia::Mask::new( - bounds.width as u32, - bounds.height as u32, - ) - .unwrap(); - - let zero_bounds = Rectangle { - x: 0.0, - y: 0.0, - width: bounds.width, - height: bounds.height, - }; - let path = - rounded_rectangle(zero_bounds, fill_border_radius); - - quad_mask.fill_path( - &path, - tiny_skia::FillRule::EvenOdd, - true, - transform, - ); - let path_bounds = Rectangle { - x: border_width / 2.0, - y: border_width / 2.0, - width: bounds.width - border_width, - height: bounds.height - border_width, - }; - - let border_radius_path = - rounded_rectangle(path_bounds, border_radius); - - temp_pixmap.stroke_path( - &border_radius_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, - Some(&quad_mask), - ); - - pixels.draw_pixmap( - bounds.x as i32, - bounds.y as i32, - temp_pixmap.as_ref(), - &tiny_skia::PixmapPaint::default(), - transform, - clip_mask, - ); - } - } - } - Primitive::Paragraph { - paragraph, - position, - color, - clip_bounds: _, // TODO: Support text clip bounds - } => { - let physical_bounds = - Rectangle::new(*position, paragraph.min_bounds) - * transformation - * scale_factor; - - if !clip_bounds.intersects(&physical_bounds) { - return; - } - - let clip_mask = (!physical_bounds.is_within(&clip_bounds)) - .then_some(clip_mask as &_); - - self.text_pipeline.draw_paragraph( - paragraph, - *position, - *color, - scale_factor, - pixels, - clip_mask, - transformation, - ); - } - Primitive::Editor { - editor, - position, - color, - clip_bounds: _, // TODO: Support text clip bounds - } => { - let physical_bounds = Rectangle::new(*position, editor.bounds) - * transformation - * scale_factor; - - if !clip_bounds.intersects(&physical_bounds) { - return; - } - - let clip_mask = (!physical_bounds.is_within(&clip_bounds)) - .then_some(clip_mask as &_); - - self.text_pipeline.draw_editor( - editor, - *position, - *color, - scale_factor, - pixels, - clip_mask, - transformation, - ); - } - Primitive::Text { - content, - bounds, - color, - size, - line_height, - font, - horizontal_alignment, - vertical_alignment, - shaping, - clip_bounds: _, // TODO: Support text clip bounds - } => { - let physical_bounds = - primitive.bounds() * transformation * scale_factor; - - if !clip_bounds.intersects(&physical_bounds) { - return; - } - - let clip_mask = (!physical_bounds.is_within(&clip_bounds)) - .then_some(clip_mask as &_); - - self.text_pipeline.draw_cached( - content, - *bounds, - *color, - *size, - *line_height, - *font, - *horizontal_alignment, - *vertical_alignment, - *shaping, - scale_factor, - pixels, - clip_mask, - transformation, - ); - } - Primitive::RawText(text::Raw { - buffer, - position, - color, - clip_bounds: _, // TODO: Support text clip bounds - }) => { - let Some(buffer) = buffer.upgrade() else { - return; - }; - - let (width, height) = buffer.size(); - - let physical_bounds = - Rectangle::new(*position, Size::new(width, height)) - * transformation - * scale_factor; - - if !clip_bounds.intersects(&physical_bounds) { - return; - } - - let clip_mask = (!physical_bounds.is_within(&clip_bounds)) - .then_some(clip_mask as &_); - - self.text_pipeline.draw_raw( - &buffer, - *position, - *color, - scale_factor, - pixels, - clip_mask, - transformation, - ); - } - #[cfg(feature = "image")] - Primitive::Image { - handle, - filter_method, - bounds, - } => { - let physical_bounds = (*bounds * transformation) * scale_factor; - - if !clip_bounds.intersects(&physical_bounds) { - return; - } - - let clip_mask = (!physical_bounds.is_within(&clip_bounds)) - .then_some(clip_mask as &_); - - let transform = into_transform(transformation) - .post_scale(scale_factor, scale_factor); - - self.raster_pipeline.draw( - handle, - *filter_method, - *bounds, - pixels, - transform, - clip_mask, - ); - } - #[cfg(not(feature = "image"))] - Primitive::Image { .. } => { - log::warn!( - "Unsupported primitive in `iced_tiny_skia`: {primitive:?}", - ); - } - #[cfg(feature = "svg")] - Primitive::Svg { - handle, - bounds, - color, - } => { - let physical_bounds = (*bounds * transformation) * scale_factor; - - if !clip_bounds.intersects(&physical_bounds) { - return; - } - - let clip_mask = (!physical_bounds.is_within(&clip_bounds)) - .then_some(clip_mask as &_); - - self.vector_pipeline.draw( - handle, - *color, - (*bounds * transformation) * scale_factor, - pixels, - clip_mask, - ); - } - #[cfg(not(feature = "svg"))] - Primitive::Svg { .. } => { - log::warn!( - "Unsupported primitive in `iced_tiny_skia`: {primitive:?}", - ); - } - Primitive::Custom(primitive::Custom::Fill { - path, - paint, - rule, - }) => { - let bounds = path.bounds(); - - let physical_bounds = (Rectangle { - x: bounds.x(), - y: bounds.y(), - width: bounds.width(), - height: bounds.height(), - } * transformation) - * scale_factor; - - if !clip_bounds.intersects(&physical_bounds) { - return; - } - - let clip_mask = (!physical_bounds.is_within(&clip_bounds)) - .then_some(clip_mask as &_); - - pixels.fill_path( - path, - paint, - *rule, - into_transform(transformation) - .post_scale(scale_factor, scale_factor), - clip_mask, - ); - } - Primitive::Custom(primitive::Custom::Stroke { - path, - paint, - stroke, - }) => { - let bounds = path.bounds(); - - let physical_bounds = (Rectangle { - x: bounds.x(), - y: bounds.y(), - width: bounds.width().max(1.0), - height: bounds.height().max(1.0), - } * transformation) - * scale_factor; - - if !clip_bounds.intersects(&physical_bounds) { - return; - } - - let clip_mask = (!physical_bounds.is_within(&clip_bounds)) - .then_some(clip_mask as &_); - - pixels.stroke_path( - path, - paint, - stroke, - into_transform(transformation) - .post_scale(scale_factor, scale_factor), - clip_mask, - ); - } - Primitive::Group { primitives } => { - for primitive in primitives { - self.draw_primitive( - primitive, - pixels, - clip_mask, - clip_bounds, - scale_factor, - transformation, - ); - } - } - Primitive::Transform { - transformation: new_transformation, - content, - } => { - self.draw_primitive( - content, - pixels, - clip_mask, - clip_bounds, - scale_factor, - transformation * *new_transformation, - ); - } - Primitive::Clip { bounds, content } => { - let bounds = (*bounds * transformation) * scale_factor; - - if bounds == clip_bounds { - self.draw_primitive( - content, - pixels, - clip_mask, - bounds, - scale_factor, - transformation, - ); - } else if let Some(bounds) = clip_bounds.intersection(&bounds) { - if bounds.x + bounds.width <= 0.0 - || bounds.y + bounds.height <= 0.0 - || bounds.x as u32 >= pixels.width() - || bounds.y as u32 >= pixels.height() - || bounds.width <= 1.0 - || bounds.height <= 1.0 - { - return; - } - - adjust_clip_mask(clip_mask, bounds); - - self.draw_primitive( - content, - pixels, - clip_mask, - bounds, - scale_factor, - transformation, - ); - - adjust_clip_mask(clip_mask, clip_bounds); - } - } - Primitive::Cache { content } => { - self.draw_primitive( - content, - pixels, - clip_mask, - clip_bounds, - scale_factor, - transformation, - ); - } - } - } -} - -impl Default for Backend { - fn default() -> Self { - Self::new() - } -} - -fn into_color(color: Color) -> tiny_skia::Color { - tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a) - .expect("Convert color from iced to tiny_skia") -} - -fn into_transform(transformation: Transformation) -> tiny_skia::Transform { - let translation = transformation.translation(); - - tiny_skia::Transform { - sx: transformation.scale_factor(), - kx: 0.0, - ky: 0.0, - sy: transformation.scale_factor(), - tx: translation.x, - ty: translation.y, - } -} - -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 == 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 - && 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 { - arc_to( - &mut builder, - bounds.x + bounds.width - top_right, - bounds.y, - bounds.x + bounds.width, - bounds.y + top_right, - top_right, - ); - } - - maybe_line_to( - &mut builder, - bounds.x + bounds.width, - bounds.y + bounds.height - bottom_right, - ); - - if bottom_right > 0.0 { - arc_to( - &mut builder, - bounds.x + bounds.width, - bounds.y + bounds.height - bottom_right, - bounds.x + bounds.width - bottom_right, - bounds.y + bounds.height, - bottom_right, - ); - } - - maybe_line_to( - &mut builder, - bounds.x + bottom_left, - bounds.y + bounds.height, - ); - - if bottom_left > 0.0 { - arc_to( - &mut builder, - bounds.x + bottom_left, - bounds.y + bounds.height, - bounds.x, - bounds.y + bounds.height - bottom_left, - bottom_left, - ); - } - - maybe_line_to(&mut builder, bounds.x, bounds.y + top_left); - - if top_left > 0.0 { - 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, y_to); - } - } -} - -fn adjust_clip_mask(clip_mask: &mut tiny_skia::Mask, bounds: Rectangle) { - clip_mask.clear(); - - let path = { - let mut builder = tiny_skia::PathBuilder::new(); - builder.push_rect( - tiny_skia::Rect::from_xywh( - bounds.x, - bounds.y, - bounds.width, - bounds.height, - ) - .unwrap(), - ); - - builder.finish().unwrap() - }; - - clip_mask.fill_path( - &path, - tiny_skia::FillRule::EvenOdd, - false, - tiny_skia::Transform::default(), - ); -} - -fn smoothstep(a: f32, b: f32, x: f32) -> f32 { - let x = ((x - a) / (b - a)).clamp(0.0, 1.0); - - x * x * (3.0 - 2.0 * x) -} - -fn rounded_box_sdf( - to_center: Vector, - size: tiny_skia::Size, - radii: &[f32], -) -> f32 { - let radius = match (to_center.x > 0.0, to_center.y > 0.0) { - (true, true) => radii[2], - (true, false) => radii[1], - (false, true) => radii[3], - (false, false) => radii[0], - }; - - let x = (to_center.x.abs() - size.width() + radius).max(0.0); - let y = (to_center.y.abs() - size.height() + radius).max(0.0); - - (x.powf(2.0) + y.powf(2.0)).sqrt() - radius -} - -impl backend::Backend for Backend { - type Primitive = primitive::Custom; - type Compositor = window::Compositor; -} - -impl backend::Text for Backend { - fn load_font(&mut self, font: Cow<'static, [u8]>) { - self.text_pipeline.load_font(font); - } -} - -#[cfg(feature = "image")] -impl backend::Image for Backend { - fn dimensions( - &self, - handle: &crate::core::image::Handle, - ) -> crate::core::Size { - self.raster_pipeline.dimensions(handle) - } -} - -#[cfg(feature = "svg")] -impl backend::Svg for Backend { - fn viewport_dimensions( - &self, - handle: &crate::core::svg::Handle, - ) -> crate::core::Size { - self.vector_pipeline.viewport_dimensions(handle) - } -} - -#[cfg(feature = "geometry")] -impl crate::graphics::geometry::Backend for Backend { - type Frame = crate::geometry::Frame; - - fn new_frame(&self, size: Size) -> Self::Frame { - crate::geometry::Frame::new(size) - } -} diff --git a/tiny_skia/src/engine.rs b/tiny_skia/src/engine.rs new file mode 100644 index 00000000..3930f46a --- /dev/null +++ b/tiny_skia/src/engine.rs @@ -0,0 +1,831 @@ +use crate::core::renderer::Quad; +use crate::core::{ + Background, Color, Gradient, Rectangle, Size, Transformation, Vector, +}; +use crate::graphics::{Image, Text}; +use crate::text; +use crate::Primitive; + +#[derive(Debug)] +pub struct Engine { + text_pipeline: text::Pipeline, + + #[cfg(feature = "image")] + pub(crate) raster_pipeline: crate::raster::Pipeline, + #[cfg(feature = "svg")] + pub(crate) vector_pipeline: crate::vector::Pipeline, +} + +impl Engine { + pub fn new() -> Self { + Self { + text_pipeline: text::Pipeline::new(), + #[cfg(feature = "image")] + raster_pipeline: crate::raster::Pipeline::new(), + #[cfg(feature = "svg")] + vector_pipeline: crate::vector::Pipeline::new(), + } + } + + pub fn draw_quad( + &mut self, + quad: &Quad, + background: &Background, + transformation: Transformation, + pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: &mut tiny_skia::Mask, + clip_bounds: Rectangle, + ) { + debug_assert!( + quad.bounds.width.is_normal(), + "Quad with non-normal width!" + ); + debug_assert!( + quad.bounds.height.is_normal(), + "Quad with non-normal height!" + ); + + let physical_bounds = quad.bounds * transformation; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + + let transform = into_transform(transformation); + + // Make sure the border radius is not larger than the bounds + let border_width = quad + .border + .width + .min(quad.bounds.width / 2.0) + .min(quad.bounds.height / 2.0); + + let mut fill_border_radius = <[f32; 4]>::from(quad.border.radius); + + for radius in &mut fill_border_radius { + *radius = (*radius) + .min(quad.bounds.width / 2.0) + .min(quad.bounds.height / 2.0); + } + + let path = rounded_rectangle(quad.bounds, fill_border_radius); + + let shadow = quad.shadow; + + if shadow.color.a > 0.0 { + let shadow_bounds = Rectangle { + x: quad.bounds.x + shadow.offset.x - shadow.blur_radius, + y: quad.bounds.y + shadow.offset.y - shadow.blur_radius, + width: quad.bounds.width + shadow.blur_radius * 2.0, + height: quad.bounds.height + shadow.blur_radius * 2.0, + } * transformation; + + let radii = fill_border_radius + .into_iter() + .map(|radius| radius * transformation.scale_factor()) + .collect::>(); + let (x, y, width, height) = ( + shadow_bounds.x as u32, + shadow_bounds.y as u32, + shadow_bounds.width as u32, + shadow_bounds.height as u32, + ); + let half_width = physical_bounds.width / 2.0; + let half_height = physical_bounds.height / 2.0; + + let colors = (y..y + height) + .flat_map(|y| (x..x + width).map(move |x| (x as f32, y as f32))) + .filter_map(|(x, y)| { + tiny_skia::Size::from_wh(half_width, half_height).map( + |size| { + let shadow_distance = rounded_box_sdf( + Vector::new( + x - physical_bounds.position().x + - (shadow.offset.x + * transformation.scale_factor()) + - half_width, + y - physical_bounds.position().y + - (shadow.offset.y + * transformation.scale_factor()) + - half_height, + ), + size, + &radii, + ) + .max(0.0); + let shadow_alpha = 1.0 + - smoothstep( + -shadow.blur_radius + * transformation.scale_factor(), + shadow.blur_radius + * transformation.scale_factor(), + shadow_distance, + ); + + let mut color = into_color(shadow.color); + color.apply_opacity(shadow_alpha); + + color.to_color_u8().premultiply() + }, + ) + }) + .collect(); + + if let Some(pixmap) = tiny_skia::IntSize::from_wh(width, height) + .and_then(|size| { + tiny_skia::Pixmap::from_vec( + bytemuck::cast_vec(colors), + size, + ) + }) + { + pixels.draw_pixmap( + x as i32, + y as i32, + pixmap.as_ref(), + &tiny_skia::PixmapPaint::default(), + tiny_skia::Transform::default(), + None, + ); + } + } + + pixels.fill_path( + &path, + &tiny_skia::Paint { + shader: match background { + Background::Color(color) => { + tiny_skia::Shader::SolidColor(into_color(*color)) + } + Background::Gradient(Gradient::Linear(linear)) => { + let (start, end) = + linear.angle.to_distance(&quad.bounds); + + let stops: Vec = linear + .stops + .into_iter() + .flatten() + .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::LinearGradient::new( + tiny_skia::Point { + x: start.x, + y: start.y, + }, + tiny_skia::Point { x: end.x, y: end.y }, + if stops.is_empty() { + vec![tiny_skia::GradientStop::new( + 0.0, + tiny_skia::Color::BLACK, + )] + } else { + stops + }, + tiny_skia::SpreadMode::Pad, + tiny_skia::Transform::identity(), + ) + .expect("Create linear gradient") + } + }, + anti_alias: true, + ..tiny_skia::Paint::default() + }, + tiny_skia::FillRule::EvenOdd, + transform, + clip_mask, + ); + + if border_width > 0.0 { + // Border path is offset by half the border width + let border_bounds = Rectangle { + x: quad.bounds.x + border_width / 2.0, + y: quad.bounds.y + border_width / 2.0, + width: quad.bounds.width - border_width, + height: quad.bounds.height - border_width, + }; + + // Make sure the border radius is correct + let mut border_radius = <[f32; 4]>::from(quad.border.radius); + let mut is_simple_border = true; + + for radius in &mut border_radius { + *radius = if *radius == 0.0 { + // Path should handle this fine + 0.0 + } else if *radius > border_width / 2.0 { + *radius - border_width / 2.0 + } else { + is_simple_border = false; + 0.0 + } + .min(border_bounds.width / 2.0) + .min(border_bounds.height / 2.0); + } + + // Stroking a path works well in this case + if is_simple_border { + let border_path = + rounded_rectangle(border_bounds, border_radius); + + pixels.stroke_path( + &border_path, + &tiny_skia::Paint { + shader: tiny_skia::Shader::SolidColor(into_color( + quad.border.color, + )), + anti_alias: true, + ..tiny_skia::Paint::default() + }, + &tiny_skia::Stroke { + width: border_width, + ..tiny_skia::Stroke::default() + }, + transform, + clip_mask, + ); + } else { + // Draw corners that have too small border radii as having no border radius, + // but mask them with the rounded rectangle with the correct border radius. + let mut temp_pixmap = tiny_skia::Pixmap::new( + quad.bounds.width as u32, + quad.bounds.height as u32, + ) + .unwrap(); + + let mut quad_mask = tiny_skia::Mask::new( + quad.bounds.width as u32, + quad.bounds.height as u32, + ) + .unwrap(); + + let zero_bounds = Rectangle { + x: 0.0, + y: 0.0, + width: quad.bounds.width, + height: quad.bounds.height, + }; + let path = rounded_rectangle(zero_bounds, fill_border_radius); + + quad_mask.fill_path( + &path, + tiny_skia::FillRule::EvenOdd, + true, + transform, + ); + let path_bounds = Rectangle { + x: border_width / 2.0, + y: border_width / 2.0, + width: quad.bounds.width - border_width, + height: quad.bounds.height - border_width, + }; + + let border_radius_path = + rounded_rectangle(path_bounds, border_radius); + + temp_pixmap.stroke_path( + &border_radius_path, + &tiny_skia::Paint { + shader: tiny_skia::Shader::SolidColor(into_color( + quad.border.color, + )), + anti_alias: true, + ..tiny_skia::Paint::default() + }, + &tiny_skia::Stroke { + width: border_width, + ..tiny_skia::Stroke::default() + }, + transform, + Some(&quad_mask), + ); + + pixels.draw_pixmap( + quad.bounds.x as i32, + quad.bounds.y as i32, + temp_pixmap.as_ref(), + &tiny_skia::PixmapPaint::default(), + transform, + clip_mask, + ); + } + } + } + + pub fn draw_text( + &mut self, + text: &Text, + transformation: Transformation, + pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: &mut tiny_skia::Mask, + clip_bounds: Rectangle, + ) { + match text { + Text::Paragraph { + paragraph, + position, + color, + clip_bounds: _, // TODO + transformation: local_transformation, + } => { + let transformation = transformation * *local_transformation; + + let physical_bounds = + Rectangle::new(*position, paragraph.min_bounds) + * transformation; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + + self.text_pipeline.draw_paragraph( + paragraph, + *position, + *color, + pixels, + clip_mask, + transformation, + ); + } + Text::Editor { + editor, + position, + color, + clip_bounds: _, // TODO + transformation: local_transformation, + } => { + let transformation = transformation * *local_transformation; + + let physical_bounds = + Rectangle::new(*position, editor.bounds) * transformation; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + + self.text_pipeline.draw_editor( + editor, + *position, + *color, + pixels, + clip_mask, + transformation, + ); + } + Text::Cached { + content, + bounds, + color, + size, + line_height, + font, + horizontal_alignment, + vertical_alignment, + shaping, + clip_bounds: _, // TODO + } => { + let physical_bounds = *bounds * transformation; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + + self.text_pipeline.draw_cached( + content, + *bounds, + *color, + *size, + *line_height, + *font, + *horizontal_alignment, + *vertical_alignment, + *shaping, + pixels, + clip_mask, + transformation, + ); + } + Text::Raw { + raw, + transformation: local_transformation, + } => { + let Some(buffer) = raw.buffer.upgrade() else { + return; + }; + + let transformation = transformation * *local_transformation; + let (width, height) = buffer.size(); + + let physical_bounds = + Rectangle::new(raw.position, Size::new(width, height)) + * transformation; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + + self.text_pipeline.draw_raw( + &buffer, + raw.position, + raw.color, + pixels, + clip_mask, + transformation, + ); + } + } + } + + pub fn draw_primitive( + &mut self, + primitive: &Primitive, + transformation: Transformation, + pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: &mut tiny_skia::Mask, + layer_bounds: Rectangle, + ) { + match primitive { + Primitive::Fill { path, paint, rule } => { + let physical_bounds = { + let bounds = path.bounds(); + + Rectangle { + x: bounds.x(), + y: bounds.y(), + width: bounds.width(), + height: bounds.height(), + } * transformation + }; + + let Some(clip_bounds) = + layer_bounds.intersection(&physical_bounds) + else { + return; + }; + + let clip_mask = + (physical_bounds != clip_bounds).then_some(clip_mask as &_); + + pixels.fill_path( + path, + paint, + *rule, + into_transform(transformation), + clip_mask, + ); + } + Primitive::Stroke { + path, + paint, + stroke, + } => { + let physical_bounds = { + let bounds = path.bounds(); + + Rectangle { + x: bounds.x(), + y: bounds.y(), + width: bounds.width(), + height: bounds.height(), + } * transformation + }; + + let Some(clip_bounds) = + layer_bounds.intersection(&physical_bounds) + else { + return; + }; + + let clip_mask = + (physical_bounds != clip_bounds).then_some(clip_mask as &_); + + pixels.stroke_path( + path, + paint, + stroke, + into_transform(transformation), + clip_mask, + ); + } + } + } + + pub fn draw_image( + &mut self, + image: &Image, + transformation: Transformation, + pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: &mut tiny_skia::Mask, + clip_bounds: Rectangle, + ) { + match image { + #[cfg(feature = "image")] + Image::Raster { + handle, + filter_method, + bounds, + } => { + let physical_bounds = *bounds * transformation; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + + self.raster_pipeline.draw( + handle, + *filter_method, + *bounds, + pixels, + into_transform(transformation), + clip_mask, + ); + } + #[cfg(feature = "svg")] + Image::Vector { + handle, + color, + bounds, + } => { + let physical_bounds = *bounds * transformation; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + + self.vector_pipeline.draw( + handle, + *color, + physical_bounds, + pixels, + clip_mask, + ); + } + #[cfg(not(feature = "image"))] + Image::Raster { .. } => { + log::warn!( + "Unsupported primitive in `iced_tiny_skia`: {image:?}", + ); + } + #[cfg(not(feature = "svg"))] + Image::Vector { .. } => { + log::warn!( + "Unsupported primitive in `iced_tiny_skia`: {image:?}", + ); + } + } + } + + pub fn trim(&mut self) { + self.text_pipeline.trim_cache(); + + #[cfg(feature = "image")] + self.raster_pipeline.trim_cache(); + + #[cfg(feature = "svg")] + self.vector_pipeline.trim_cache(); + } +} + +pub fn into_color(color: Color) -> tiny_skia::Color { + tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a) + .expect("Convert color from iced to tiny_skia") +} + +fn into_transform(transformation: Transformation) -> tiny_skia::Transform { + let translation = transformation.translation(); + + tiny_skia::Transform { + sx: transformation.scale_factor(), + kx: 0.0, + ky: 0.0, + sy: transformation.scale_factor(), + tx: translation.x, + ty: translation.y, + } +} + +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 == 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 + && 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 { + arc_to( + &mut builder, + bounds.x + bounds.width - top_right, + bounds.y, + bounds.x + bounds.width, + bounds.y + top_right, + top_right, + ); + } + + maybe_line_to( + &mut builder, + bounds.x + bounds.width, + bounds.y + bounds.height - bottom_right, + ); + + if bottom_right > 0.0 { + arc_to( + &mut builder, + bounds.x + bounds.width, + bounds.y + bounds.height - bottom_right, + bounds.x + bounds.width - bottom_right, + bounds.y + bounds.height, + bottom_right, + ); + } + + maybe_line_to( + &mut builder, + bounds.x + bottom_left, + bounds.y + bounds.height, + ); + + if bottom_left > 0.0 { + arc_to( + &mut builder, + bounds.x + bottom_left, + bounds.y + bounds.height, + bounds.x, + bounds.y + bounds.height - bottom_left, + bottom_left, + ); + } + + maybe_line_to(&mut builder, bounds.x, bounds.y + top_left); + + if top_left > 0.0 { + 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, y_to); + } + } +} + +fn smoothstep(a: f32, b: f32, x: f32) -> f32 { + let x = ((x - a) / (b - a)).clamp(0.0, 1.0); + + x * x * (3.0 - 2.0 * x) +} + +fn rounded_box_sdf( + to_center: Vector, + size: tiny_skia::Size, + radii: &[f32], +) -> f32 { + let radius = match (to_center.x > 0.0, to_center.y > 0.0) { + (true, true) => radii[2], + (true, false) => radii[1], + (false, true) => radii[3], + (false, false) => radii[0], + }; + + let x = (to_center.x.abs() - size.width() + radius).max(0.0); + let y = (to_center.y.abs() - size.height() + radius).max(0.0); + + (x.powf(2.0) + y.powf(2.0)).sqrt() - radius +} + +pub fn adjust_clip_mask(clip_mask: &mut tiny_skia::Mask, bounds: Rectangle) { + clip_mask.clear(); + + let path = { + let mut builder = tiny_skia::PathBuilder::new(); + builder.push_rect( + tiny_skia::Rect::from_xywh( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ) + .unwrap(), + ); + + builder.finish().unwrap() + }; + + clip_mask.fill_path( + &path, + tiny_skia::FillRule::EvenOdd, + false, + tiny_skia::Transform::default(), + ); +} diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index a9dcb610..04aabf0d 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -1,58 +1,98 @@ use crate::core::text::LineHeight; -use crate::core::{ - Pixels, Point, Radians, Rectangle, Size, Transformation, Vector, -}; +use crate::core::{Pixels, Point, Radians, Rectangle, Size, Vector}; use crate::graphics::geometry::fill::{self, Fill}; use crate::graphics::geometry::stroke::{self, Stroke}; -use crate::graphics::geometry::{self, Path, Style, Text}; -use crate::graphics::Gradient; -use crate::primitive::{self, Primitive}; +use crate::graphics::geometry::{self, Path, Style}; +use crate::graphics::{Cached, Gradient, Text}; +use crate::Primitive; + +use std::rc::Rc; + +#[derive(Debug)] +pub enum Geometry { + Live { + text: Vec, + primitives: Vec, + clip_bounds: Rectangle, + }, + Cache(Cache), +} + +#[derive(Debug, Clone)] +pub struct Cache { + pub text: Rc<[Text]>, + pub primitives: Rc<[Primitive]>, + pub clip_bounds: Rectangle, +} + +impl Cached for Geometry { + type Cache = Cache; + + fn load(cache: &Cache) -> Self { + Self::Cache(cache.clone()) + } + + fn cache(self, _previous: Option) -> Cache { + match self { + Self::Live { + primitives, + text, + clip_bounds, + } => Cache { + primitives: Rc::from(primitives), + text: Rc::from(text), + clip_bounds, + }, + Self::Cache(cache) => cache, + } + } +} #[derive(Debug)] pub struct Frame { - size: Size, + clip_bounds: Rectangle, transform: tiny_skia::Transform, stack: Vec, primitives: Vec, + text: Vec, } impl Frame { pub fn new(size: Size) -> Self { + Self::with_clip(Rectangle::with_size(size)) + } + + pub fn with_clip(clip_bounds: Rectangle) -> Self { Self { - size, - transform: tiny_skia::Transform::identity(), + clip_bounds, stack: Vec::new(), primitives: Vec::new(), - } - } - - pub fn into_primitive(self) -> Primitive { - Primitive::Clip { - bounds: Rectangle::new(Point::ORIGIN, self.size), - content: Box::new(Primitive::Group { - primitives: self.primitives, - }), + text: Vec::new(), + transform: tiny_skia::Transform::from_translate( + clip_bounds.x, + clip_bounds.y, + ), } } } impl geometry::frame::Backend for Frame { - type Geometry = Primitive; + type Geometry = Geometry; fn width(&self) -> f32 { - self.size.width + self.clip_bounds.width } fn height(&self) -> f32 { - self.size.height + self.clip_bounds.height } fn size(&self) -> Size { - self.size + self.clip_bounds.size() } fn center(&self) -> Point { - Point::new(self.size.width / 2.0, self.size.height / 2.0) + Point::new(self.clip_bounds.width / 2.0, self.clip_bounds.height / 2.0) } fn fill(&mut self, path: &Path, fill: impl Into) { @@ -67,12 +107,11 @@ impl geometry::frame::Backend for Frame { let mut paint = into_paint(fill.style); paint.shader.transform(self.transform); - self.primitives - .push(Primitive::Custom(primitive::Custom::Fill { - path, - paint, - rule: into_fill_rule(fill.rule), - })); + self.primitives.push(Primitive::Fill { + path, + paint, + rule: into_fill_rule(fill.rule), + }); } fn fill_rectangle( @@ -95,12 +134,11 @@ impl geometry::frame::Backend for Frame { }; paint.shader.transform(self.transform); - self.primitives - .push(Primitive::Custom(primitive::Custom::Fill { - path, - paint, - rule: into_fill_rule(fill.rule), - })); + self.primitives.push(Primitive::Fill { + path, + paint, + rule: into_fill_rule(fill.rule), + }); } fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { @@ -116,15 +154,14 @@ impl geometry::frame::Backend for Frame { let mut paint = into_paint(stroke.style); paint.shader.transform(self.transform); - self.primitives - .push(Primitive::Custom(primitive::Custom::Stroke { - path, - paint, - stroke: skia_stroke, - })); + self.primitives.push(Primitive::Stroke { + path, + paint, + stroke: skia_stroke, + }); } - fn fill_text(&mut self, text: impl Into) { + fn fill_text(&mut self, text: impl Into) { let text = text.into(); let (scale_x, scale_y) = self.transform.get_scale(); @@ -171,12 +208,12 @@ impl geometry::frame::Backend for Frame { }; // TODO: Honor layering! - self.primitives.push(Primitive::Text { + self.text.push(Text::Cached { content: text.content, bounds, color: text.color, size, - line_height, + line_height: line_height.to_absolute(size), font: text.font, horizontal_alignment: text.horizontal_alignment, vertical_alignment: text.vertical_alignment, @@ -197,14 +234,12 @@ impl geometry::frame::Backend for Frame { } fn draft(&mut self, clip_bounds: Rectangle) -> Self { - Self::new(clip_bounds.size()) + Self::with_clip(clip_bounds) } - fn paste(&mut self, frame: Self, at: Point) { - self.primitives.push(Primitive::Transform { - transformation: Transformation::translate(at.x, at.y), - content: Box::new(frame.into_primitive()), - }); + fn paste(&mut self, frame: Self, _at: Point) { + self.primitives.extend(frame.primitives); + self.text.extend(frame.text); } fn translate(&mut self, translation: Vector) { @@ -230,8 +265,12 @@ impl geometry::frame::Backend for Frame { self.transform = self.transform.pre_scale(scale.x, scale.y); } - fn into_geometry(self) -> Self::Geometry { - self.into_primitive() + fn into_geometry(self) -> Geometry { + Geometry::Live { + primitives: self.primitives, + text: self.text, + clip_bounds: self.clip_bounds, + } } } diff --git a/tiny_skia/src/layer.rs b/tiny_skia/src/layer.rs new file mode 100644 index 00000000..1f5c8b46 --- /dev/null +++ b/tiny_skia/src/layer.rs @@ -0,0 +1,243 @@ +use crate::core::image; +use crate::core::renderer::Quad; +use crate::core::svg; +use crate::core::{Background, Color, Point, Rectangle, Transformation}; +use crate::graphics::layer; +use crate::graphics::text::{Editor, Paragraph, Text}; +use crate::graphics::{self, Image}; +use crate::Primitive; + +use std::rc::Rc; + +pub type Stack = layer::Stack; + +#[derive(Debug, Clone)] +pub struct Layer { + pub bounds: Rectangle, + pub quads: Vec<(Quad, Background)>, + pub primitives: Vec>, + pub text: Vec>, + pub images: Vec, +} + +#[derive(Debug, Clone)] +pub enum Item { + Live(T), + Group(Vec, Rectangle, Transformation), + Cached(Rc<[T]>, Rectangle, Transformation), +} + +impl Item { + pub fn transformation(&self) -> Transformation { + match self { + Item::Live(_) => Transformation::IDENTITY, + Item::Group(_, _, transformation) + | Item::Cached(_, _, transformation) => *transformation, + } + } + + pub fn clip_bounds(&self) -> Rectangle { + match self { + Item::Live(_) => Rectangle::INFINITE, + Item::Group(_, clip_bounds, _) + | Item::Cached(_, clip_bounds, _) => *clip_bounds, + } + } + + pub fn as_slice(&self) -> &[T] { + match self { + Item::Live(item) => std::slice::from_ref(item), + Item::Group(group, _, _) => group.as_slice(), + Item::Cached(cache, _, _) => cache, + } + } +} + +impl Layer { + pub fn draw_quad( + &mut self, + mut quad: Quad, + background: Background, + transformation: Transformation, + ) { + quad.bounds = quad.bounds * transformation; + self.quads.push((quad, background)); + } + + pub fn draw_paragraph( + &mut self, + paragraph: &Paragraph, + position: Point, + color: Color, + clip_bounds: Rectangle, + transformation: Transformation, + ) { + let paragraph = Text::Paragraph { + paragraph: paragraph.downgrade(), + position, + color, + clip_bounds, + transformation, + }; + + self.text.push(Item::Live(paragraph)); + } + + pub fn draw_editor( + &mut self, + editor: &Editor, + position: Point, + color: Color, + clip_bounds: Rectangle, + transformation: Transformation, + ) { + let editor = Text::Editor { + editor: editor.downgrade(), + position, + color, + clip_bounds, + transformation, + }; + + self.text.push(Item::Live(editor)); + } + + pub fn draw_text( + &mut self, + text: crate::core::Text, + position: Point, + color: Color, + clip_bounds: Rectangle, + transformation: Transformation, + ) { + let text = Text::Cached { + content: text.content, + bounds: Rectangle::new(position, text.bounds) * transformation, + color, + size: text.size * transformation.scale_factor(), + line_height: text.line_height.to_absolute(text.size) + * transformation.scale_factor(), + font: text.font, + horizontal_alignment: text.horizontal_alignment, + vertical_alignment: text.vertical_alignment, + shaping: text.shaping, + clip_bounds: clip_bounds * transformation, + }; + + self.text.push(Item::Live(text)); + } + + pub fn draw_text_group( + &mut self, + text: Vec, + clip_bounds: Rectangle, + transformation: Transformation, + ) { + self.text + .push(Item::Group(text, clip_bounds, transformation)); + } + + pub fn draw_text_cache( + &mut self, + text: Rc<[Text]>, + clip_bounds: Rectangle, + transformation: Transformation, + ) { + self.text + .push(Item::Cached(text, clip_bounds, transformation)); + } + + pub fn draw_image( + &mut self, + handle: image::Handle, + filter_method: image::FilterMethod, + bounds: Rectangle, + transformation: Transformation, + ) { + let image = Image::Raster { + handle, + filter_method, + bounds: bounds * transformation, + }; + + self.images.push(image); + } + + pub fn draw_svg( + &mut self, + handle: svg::Handle, + color: Option, + bounds: Rectangle, + transformation: Transformation, + ) { + let svg = Image::Vector { + handle, + color, + bounds: bounds * transformation, + }; + + self.images.push(svg); + } + + pub fn draw_primitive_group( + &mut self, + primitives: Vec, + clip_bounds: Rectangle, + transformation: Transformation, + ) { + self.primitives.push(Item::Group( + primitives, + clip_bounds, + transformation, + )); + } + + pub fn draw_primitive_cache( + &mut self, + primitives: Rc<[Primitive]>, + clip_bounds: Rectangle, + transformation: Transformation, + ) { + self.primitives.push(Item::Cached( + primitives, + clip_bounds, + transformation, + )); + } +} + +impl Default for Layer { + fn default() -> Self { + Self { + bounds: Rectangle::INFINITE, + quads: Vec::new(), + primitives: Vec::new(), + text: Vec::new(), + images: Vec::new(), + } + } +} + +impl graphics::Layer for Layer { + fn with_bounds(bounds: Rectangle) -> Self { + Self { + bounds, + ..Self::default() + } + } + + fn flush(&mut self) {} + + fn resize(&mut self, bounds: graphics::core::Rectangle) { + self.bounds = bounds; + } + + fn reset(&mut self) { + self.bounds = Rectangle::INFINITE; + + self.quads.clear(); + self.text.clear(); + self.primitives.clear(); + self.images.clear(); + } +} diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index d1f68daa..fed2f32c 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -2,7 +2,8 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] pub mod window; -mod backend; +mod engine; +mod layer; mod primitive; mod settings; mod text; @@ -19,12 +20,388 @@ pub mod geometry; pub use iced_graphics as graphics; pub use iced_graphics::core; -pub use backend::Backend; +pub use layer::Layer; pub use primitive::Primitive; pub use settings::Settings; +#[cfg(feature = "geometry")] +pub use geometry::Geometry; + +use crate::core::renderer; +use crate::core::{ + Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, +}; +use crate::engine::Engine; +use crate::graphics::compositor; +use crate::graphics::text::{Editor, Paragraph}; +use crate::graphics::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; +#[derive(Debug)] +pub struct Renderer { + default_font: Font, + default_text_size: Pixels, + layers: layer::Stack, + engine: Engine, // TODO: Shared engine +} + +impl Renderer { + pub fn new(default_font: Font, default_text_size: Pixels) -> Self { + Self { + default_font, + default_text_size, + layers: layer::Stack::new(), + engine: Engine::new(), + } + } + + pub fn layers(&mut self) -> impl Iterator { + self.layers.flush(); + self.layers.iter() + } + + pub fn draw>( + &mut self, + pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: &mut tiny_skia::Mask, + viewport: &Viewport, + damage: &[Rectangle], + background_color: Color, + overlay: &[T], + ) { + let physical_size = viewport.physical_size(); + let scale_factor = viewport.scale_factor() as f32; + + if !overlay.is_empty() { + let path = tiny_skia::PathBuilder::from_rect( + tiny_skia::Rect::from_xywh( + 0.0, + 0.0, + physical_size.width as f32, + physical_size.height as f32, + ) + .expect("Create damage rectangle"), + ); + + pixels.fill_path( + &path, + &tiny_skia::Paint { + shader: tiny_skia::Shader::SolidColor(engine::into_color( + Color { + a: 0.1, + ..background_color + }, + )), + anti_alias: false, + ..Default::default() + }, + tiny_skia::FillRule::default(), + tiny_skia::Transform::identity(), + None, + ); + } + + self.layers.flush(); + + for ®ion in damage { + let region = region * scale_factor; + + let path = tiny_skia::PathBuilder::from_rect( + tiny_skia::Rect::from_xywh( + region.x, + region.y, + region.width, + region.height, + ) + .expect("Create damage rectangle"), + ); + + pixels.fill_path( + &path, + &tiny_skia::Paint { + shader: tiny_skia::Shader::SolidColor(engine::into_color( + background_color, + )), + anti_alias: false, + blend_mode: tiny_skia::BlendMode::Source, + ..Default::default() + }, + tiny_skia::FillRule::default(), + tiny_skia::Transform::identity(), + None, + ); + + for layer in self.layers.iter() { + let Some(clip_bounds) = region.intersection(&layer.bounds) + else { + continue; + }; + + let clip_bounds = clip_bounds * scale_factor; + engine::adjust_clip_mask(clip_mask, clip_bounds); + + for (quad, background) in &layer.quads { + self.engine.draw_quad( + quad, + background, + Transformation::scale(scale_factor), + pixels, + clip_mask, + clip_bounds, + ); + } + + for group in &layer.text { + for text in group.as_slice() { + self.engine.draw_text( + text, + group.transformation() + * Transformation::scale(scale_factor), + pixels, + clip_mask, + clip_bounds, + ); + } + } + + for group in &layer.primitives { + let Some(new_clip_bounds) = + group.clip_bounds().intersection(&layer.bounds) + else { + continue; + }; + + engine::adjust_clip_mask( + clip_mask, + new_clip_bounds * scale_factor, + ); + + for primitive in group.as_slice() { + self.engine.draw_primitive( + primitive, + group.transformation() + * Transformation::scale(scale_factor), + pixels, + clip_mask, + clip_bounds, + ); + } + + engine::adjust_clip_mask(clip_mask, clip_bounds); + } + + for image in &layer.images { + self.engine.draw_image( + image, + Transformation::scale(scale_factor), + pixels, + clip_mask, + clip_bounds, + ); + } + } + + if !overlay.is_empty() { + pixels.stroke_path( + &path, + &tiny_skia::Paint { + shader: tiny_skia::Shader::SolidColor( + engine::into_color(Color::from_rgb(1.0, 0.0, 0.0)), + ), + anti_alias: false, + ..tiny_skia::Paint::default() + }, + &tiny_skia::Stroke { + width: 1.0, + ..tiny_skia::Stroke::default() + }, + tiny_skia::Transform::identity(), + None, + ); + } + } + + self.engine.trim(); + } +} + +impl core::Renderer for Renderer { + fn start_layer(&mut self, bounds: Rectangle) { + self.layers.push_clip(bounds); + } + + fn end_layer(&mut self) { + self.layers.pop_clip(); + } + + fn start_transformation(&mut self, transformation: Transformation) { + self.layers.push_transformation(transformation); + } + + fn end_transformation(&mut self) { + self.layers.pop_transformation(); + } + + fn fill_quad( + &mut self, + quad: renderer::Quad, + background: impl Into, + ) { + let (layer, transformation) = self.layers.current_mut(); + layer.draw_quad(quad, background.into(), transformation); + } + + fn clear(&mut self) { + self.layers.clear(); + } +} + +impl core::text::Renderer for Renderer { + type Font = Font; + type Paragraph = Paragraph; + type Editor = Editor; + + const ICON_FONT: Font = Font::with_name("Iced-Icons"); + const CHECKMARK_ICON: char = '\u{f00c}'; + const ARROW_DOWN_ICON: char = '\u{e800}'; + + fn default_font(&self) -> Self::Font { + self.default_font + } + + fn default_size(&self) -> Pixels { + self.default_text_size + } + + fn fill_paragraph( + &mut self, + text: &Self::Paragraph, + position: Point, + color: Color, + clip_bounds: Rectangle, + ) { + let (layer, transformation) = self.layers.current_mut(); + + layer.draw_paragraph( + text, + position, + color, + clip_bounds, + transformation, + ); + } + + fn fill_editor( + &mut self, + editor: &Self::Editor, + position: Point, + color: Color, + clip_bounds: Rectangle, + ) { + let (layer, transformation) = self.layers.current_mut(); + layer.draw_editor(editor, position, color, clip_bounds, transformation); + } + + fn fill_text( + &mut self, + text: core::Text, + position: Point, + color: Color, + clip_bounds: Rectangle, + ) { + let (layer, transformation) = self.layers.current_mut(); + layer.draw_text(text, position, color, clip_bounds, transformation); + } +} + +#[cfg(feature = "geometry")] +impl graphics::geometry::Renderer for Renderer { + type Geometry = Geometry; + type Frame = geometry::Frame; + + fn new_frame(&self, size: core::Size) -> Self::Frame { + geometry::Frame::new(size) + } + + fn draw_geometry(&mut self, geometry: Self::Geometry) { + let (layer, transformation) = self.layers.current_mut(); + + match geometry { + Geometry::Live { + primitives, + text, + clip_bounds, + } => { + layer.draw_primitive_group( + primitives, + clip_bounds, + transformation, + ); + + layer.draw_text_group(text, clip_bounds, transformation); + } + Geometry::Cache(cache) => { + layer.draw_primitive_cache( + cache.primitives, + cache.clip_bounds, + transformation, + ); + + layer.draw_text_cache( + cache.text, + cache.clip_bounds, + transformation, + ); + } + } + } +} + +impl graphics::mesh::Renderer for Renderer { + fn draw_mesh(&mut self, _mesh: graphics::Mesh) { + log::warn!("iced_tiny_skia does not support drawing meshes"); + } +} + +#[cfg(feature = "image")] +impl core::image::Renderer for Renderer { + type Handle = core::image::Handle; + + fn measure_image(&self, handle: &Self::Handle) -> Size { + self.engine.raster_pipeline.dimensions(handle) + } + + fn draw_image( + &mut self, + handle: Self::Handle, + filter_method: core::image::FilterMethod, + bounds: Rectangle, + ) { + let (layer, transformation) = self.layers.current_mut(); + layer.draw_image(handle, filter_method, bounds, transformation); + } +} + +#[cfg(feature = "svg")] +impl core::svg::Renderer for Renderer { + fn measure_svg(&self, handle: &core::svg::Handle) -> Size { + self.engine.vector_pipeline.viewport_dimensions(handle) + } + + fn draw_svg( + &mut self, + handle: core::svg::Handle, + color: Option, + bounds: Rectangle, + ) { + let (layer, transformation) = self.layers.current_mut(); + layer.draw_svg(handle, color, bounds, transformation); + } +} + +impl compositor::Default for Renderer { + type Compositor = window::Compositor; +} diff --git a/tiny_skia/src/primitive.rs b/tiny_skia/src/primitive.rs index b7c428e4..5305788d 100644 --- a/tiny_skia/src/primitive.rs +++ b/tiny_skia/src/primitive.rs @@ -1,10 +1,5 @@ -use crate::core::Rectangle; -use crate::graphics::{Damage, Mesh}; - -pub type Primitive = crate::graphics::Primitive; - #[derive(Debug, Clone, PartialEq)] -pub enum Custom { +pub enum Primitive { /// A path filled with some paint. Fill { /// The path to fill. @@ -24,29 +19,3 @@ pub enum Custom { stroke: tiny_skia::Stroke, }, } - -impl Damage for Custom { - fn bounds(&self) -> Rectangle { - match self { - Self::Fill { path, .. } | Self::Stroke { path, .. } => { - let bounds = path.bounds(); - - Rectangle { - x: bounds.x(), - y: bounds.y(), - width: bounds.width(), - height: bounds.height(), - } - .expand(1.0) - } - } - } -} - -impl TryFrom for Custom { - type Error = &'static str; - - fn try_from(_mesh: Mesh) -> Result { - Err("unsupported") - } -} diff --git a/tiny_skia/src/settings.rs b/tiny_skia/src/settings.rs index 01d015b4..672c49f3 100644 --- a/tiny_skia/src/settings.rs +++ b/tiny_skia/src/settings.rs @@ -1,9 +1,9 @@ use crate::core::{Font, Pixels}; use crate::graphics; -/// The settings of a [`Backend`]. +/// The settings of a [`Compositor`]. /// -/// [`Backend`]: crate::Backend +/// [`Compositor`]: crate::window::Compositor #[derive(Debug, Clone, Copy, PartialEq)] pub struct Settings { /// The default [`Font`] to use. diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 66ee88da..c71deb10 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -1,5 +1,5 @@ use crate::core::alignment; -use crate::core::text::{LineHeight, Shaping}; +use crate::core::text::Shaping; use crate::core::{ Color, Font, Pixels, Point, Rectangle, Size, Transformation, }; @@ -27,6 +27,8 @@ impl Pipeline { } } + // TODO: Shared engine + #[allow(dead_code)] pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { font_system() .write() @@ -41,7 +43,6 @@ impl Pipeline { paragraph: ¶graph::Weak, position: Point, color: Color, - scale_factor: f32, pixels: &mut tiny_skia::PixmapMut<'_>, clip_mask: Option<&tiny_skia::Mask>, transformation: Transformation, @@ -62,7 +63,6 @@ impl Pipeline { color, paragraph.horizontal_alignment(), paragraph.vertical_alignment(), - scale_factor, pixels, clip_mask, transformation, @@ -74,7 +74,6 @@ impl Pipeline { editor: &editor::Weak, position: Point, color: Color, - scale_factor: f32, pixels: &mut tiny_skia::PixmapMut<'_>, clip_mask: Option<&tiny_skia::Mask>, transformation: Transformation, @@ -95,7 +94,6 @@ impl Pipeline { color, alignment::Horizontal::Left, alignment::Vertical::Top, - scale_factor, pixels, clip_mask, transformation, @@ -108,17 +106,16 @@ impl Pipeline { bounds: Rectangle, color: Color, size: Pixels, - line_height: LineHeight, + line_height: Pixels, font: Font, horizontal_alignment: alignment::Horizontal, vertical_alignment: alignment::Vertical, shaping: Shaping, - scale_factor: f32, pixels: &mut tiny_skia::PixmapMut<'_>, clip_mask: Option<&tiny_skia::Mask>, transformation: Transformation, ) { - let line_height = f32::from(line_height.to_absolute(size)); + let line_height = f32::from(line_height); let mut font_system = font_system().write().expect("Write font system"); let font_system = font_system.raw(); @@ -149,7 +146,6 @@ impl Pipeline { color, horizontal_alignment, vertical_alignment, - scale_factor, pixels, clip_mask, transformation, @@ -161,7 +157,6 @@ impl Pipeline { buffer: &cosmic_text::Buffer, position: Point, color: Color, - scale_factor: f32, pixels: &mut tiny_skia::PixmapMut<'_>, clip_mask: Option<&tiny_skia::Mask>, transformation: Transformation, @@ -178,7 +173,6 @@ impl Pipeline { color, alignment::Horizontal::Left, alignment::Vertical::Top, - scale_factor, pixels, clip_mask, transformation, @@ -199,12 +193,11 @@ fn draw( color: Color, horizontal_alignment: alignment::Horizontal, vertical_alignment: alignment::Vertical, - scale_factor: f32, pixels: &mut tiny_skia::PixmapMut<'_>, clip_mask: Option<&tiny_skia::Mask>, transformation: Transformation, ) { - let bounds = bounds * transformation * scale_factor; + let bounds = bounds * transformation; let x = match horizontal_alignment { alignment::Horizontal::Left => bounds.x, @@ -222,8 +215,8 @@ fn draw( for run in buffer.layout_runs() { for glyph in run.glyphs { - let physical_glyph = glyph - .physical((x, y), scale_factor * transformation.scale_factor()); + let physical_glyph = + glyph.physical((x, y), transformation.scale_factor()); if let Some((buffer, placement)) = glyph_cache.allocate( physical_glyph.cache_key, @@ -247,10 +240,8 @@ fn draw( pixels.draw_pixmap( physical_glyph.x + placement.left, physical_glyph.y - placement.top - + (run.line_y - * scale_factor - * transformation.scale_factor()) - .round() as i32, + + (run.line_y * transformation.scale_factor()).round() + as i32, pixmap, &tiny_skia::PixmapPaint { opacity, diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 2350adb9..e1a9dfec 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -1,9 +1,8 @@ use crate::core::{Color, Rectangle, Size}; use crate::graphics::compositor::{self, Information}; -use crate::graphics::damage; use crate::graphics::error::{self, Error}; use crate::graphics::{self, Viewport}; -use crate::{Backend, Primitive, Renderer, Settings}; +use crate::{Layer, Renderer, Settings}; use std::collections::VecDeque; use std::num::NonZeroU32; @@ -21,7 +20,7 @@ pub struct Surface { Box, >, clip_mask: tiny_skia::Mask, - primitive_stack: VecDeque>, + layer_stack: VecDeque>, background_color: Color, max_age: u8, } @@ -50,7 +49,6 @@ impl crate::graphics::Compositor for Compositor { fn create_renderer(&self) -> Self::Renderer { Renderer::new( - Backend::new(), self.settings.default_font, self.settings.default_text_size, ) @@ -72,7 +70,7 @@ impl crate::graphics::Compositor for Compositor { window, clip_mask: tiny_skia::Mask::new(width, height) .expect("Create clip mask"), - primitive_stack: VecDeque::new(), + layer_stack: VecDeque::new(), background_color: Color::BLACK, max_age: 0, }; @@ -98,7 +96,7 @@ impl crate::graphics::Compositor for Compositor { surface.clip_mask = tiny_skia::Mask::new(width, height).expect("Create clip mask"); - surface.primitive_stack.clear(); + surface.layer_stack.clear(); } fn fetch_information(&self) -> Information { @@ -116,16 +114,7 @@ impl crate::graphics::Compositor for Compositor { background_color: Color, overlay: &[T], ) -> Result<(), compositor::SurfaceError> { - renderer.with_primitives(|backend, primitives| { - present( - backend, - surface, - primitives, - viewport, - background_color, - overlay, - ) - }) + present(renderer, surface, viewport, background_color, overlay) } fn screenshot>( @@ -136,16 +125,7 @@ impl crate::graphics::Compositor for Compositor { background_color: Color, overlay: &[T], ) -> Vec { - renderer.with_primitives(|backend, primitives| { - screenshot( - surface, - backend, - primitives, - viewport, - background_color, - overlay, - ) - }) + screenshot(renderer, surface, viewport, background_color, overlay) } } @@ -161,49 +141,52 @@ pub fn new( } pub fn present>( - backend: &mut Backend, + renderer: &mut Renderer, surface: &mut Surface, - primitives: &[Primitive], viewport: &Viewport, background_color: Color, overlay: &[T], ) -> Result<(), compositor::SurfaceError> { let physical_size = viewport.physical_size(); - let scale_factor = viewport.scale_factor() as f32; let mut buffer = surface .window .buffer_mut() .map_err(|_| compositor::SurfaceError::Lost)?; - let last_primitives = { + let _last_layers = { let age = buffer.age(); surface.max_age = surface.max_age.max(age); - surface.primitive_stack.truncate(surface.max_age as usize); + surface.layer_stack.truncate(surface.max_age as usize); if age > 0 { - surface.primitive_stack.get(age as usize - 1) + surface.layer_stack.get(age as usize - 1) } else { None } }; - let damage = last_primitives - .and_then(|last_primitives| { - (surface.background_color == background_color) - .then(|| damage::list(last_primitives, primitives)) - }) - .unwrap_or_else(|| vec![Rectangle::with_size(viewport.logical_size())]); + // TODO + // let damage = last_layers + // .and_then(|last_layers| { + // (surface.background_color == background_color) + // .then(|| damage::layers(last_layers, renderer.layers())) + // }) + // .unwrap_or_else(|| vec![Rectangle::with_size(viewport.logical_size())]); + + let damage = vec![Rectangle::with_size(viewport.logical_size())]; if damage.is_empty() { return Ok(()); } - surface.primitive_stack.push_front(primitives.to_vec()); + surface + .layer_stack + .push_front(renderer.layers().cloned().collect()); surface.background_color = background_color; - let damage = damage::group(damage, scale_factor, physical_size); + // let damage = damage::group(damage, viewport.logical_size()); let mut pixels = tiny_skia::PixmapMut::from_bytes( bytemuck::cast_slice_mut(&mut buffer), @@ -212,10 +195,9 @@ pub fn present>( ) .expect("Create pixel map"); - backend.draw( + renderer.draw( &mut pixels, &mut surface.clip_mask, - primitives, viewport, &damage, background_color, @@ -226,9 +208,8 @@ pub fn present>( } pub fn screenshot>( + renderer: &mut Renderer, surface: &mut Surface, - backend: &mut Backend, - primitives: &[Primitive], viewport: &Viewport, background_color: Color, overlay: &[T], @@ -238,7 +219,7 @@ pub fn screenshot>( let mut offscreen_buffer: Vec = vec![0; size.width as usize * size.height as usize]; - backend.draw( + renderer.draw( &mut tiny_skia::PixmapMut::from_bytes( bytemuck::cast_slice_mut(&mut offscreen_buffer), size.width, @@ -246,7 +227,6 @@ pub fn screenshot>( ) .expect("Create offscreen pixel map"), &mut surface.clip_mask, - primitives, viewport, &[Rectangle::with_size(Size::new( size.width as f32, -- cgit From 1e802e776cb591f3860d1bfbaa1423d356fc8456 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 10 Apr 2024 15:21:42 +0200 Subject: Reintroduce damage tracking for `iced_tiny_skia` --- tiny_skia/src/engine.rs | 34 ++++----- tiny_skia/src/geometry.rs | 2 +- tiny_skia/src/layer.rs | 144 ++++++++++++++++++++++++++++--------- tiny_skia/src/lib.rs | 53 +++++++------- tiny_skia/src/primitive.rs | 19 +++++ tiny_skia/src/window/compositor.rs | 31 ++++---- 6 files changed, 191 insertions(+), 92 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/engine.rs b/tiny_skia/src/engine.rs index 3930f46a..fbca1274 100644 --- a/tiny_skia/src/engine.rs +++ b/tiny_skia/src/engine.rs @@ -402,9 +402,9 @@ impl Engine { horizontal_alignment, vertical_alignment, shaping, - clip_bounds: _, // TODO + clip_bounds: text_bounds, // TODO } => { - let physical_bounds = *bounds * transformation; + let physical_bounds = *text_bounds * transformation; if !clip_bounds.intersects(&physical_bounds) { return; @@ -539,10 +539,10 @@ impl Engine { pub fn draw_image( &mut self, image: &Image, - transformation: Transformation, - pixels: &mut tiny_skia::PixmapMut<'_>, - clip_mask: &mut tiny_skia::Mask, - clip_bounds: Rectangle, + _transformation: Transformation, + _pixels: &mut tiny_skia::PixmapMut<'_>, + _clip_mask: &mut tiny_skia::Mask, + _clip_bounds: Rectangle, ) { match image { #[cfg(feature = "image")] @@ -551,21 +551,21 @@ impl Engine { filter_method, bounds, } => { - let physical_bounds = *bounds * transformation; + let physical_bounds = *bounds * _transformation; - if !clip_bounds.intersects(&physical_bounds) { + if !_clip_bounds.intersects(&physical_bounds) { return; } - let clip_mask = (!physical_bounds.is_within(&clip_bounds)) - .then_some(clip_mask as &_); + let clip_mask = (!physical_bounds.is_within(&_clip_bounds)) + .then_some(_clip_mask as &_); self.raster_pipeline.draw( handle, *filter_method, *bounds, - pixels, - into_transform(transformation), + _pixels, + into_transform(_transformation), clip_mask, ); } @@ -575,20 +575,20 @@ impl Engine { color, bounds, } => { - let physical_bounds = *bounds * transformation; + let physical_bounds = *bounds * _transformation; - if !clip_bounds.intersects(&physical_bounds) { + if !_clip_bounds.intersects(&physical_bounds) { return; } - let clip_mask = (!physical_bounds.is_within(&clip_bounds)) - .then_some(clip_mask as &_); + let clip_mask = (!physical_bounds.is_within(&_clip_bounds)) + .then_some(_clip_mask as &_); self.vector_pipeline.draw( handle, *color, physical_bounds, - pixels, + _pixels, clip_mask, ); } diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 04aabf0d..117daf41 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -166,7 +166,7 @@ impl geometry::frame::Backend for Frame { let (scale_x, scale_y) = self.transform.get_scale(); - if self.transform.is_scale_translate() + if !self.transform.has_skew() && scale_x == scale_y && scale_x > 0.0 && scale_y > 0.0 diff --git a/tiny_skia/src/layer.rs b/tiny_skia/src/layer.rs index 1f5c8b46..0d770d81 100644 --- a/tiny_skia/src/layer.rs +++ b/tiny_skia/src/layer.rs @@ -2,6 +2,7 @@ use crate::core::image; use crate::core::renderer::Quad; use crate::core::svg; use crate::core::{Background, Color, Point, Rectangle, Transformation}; +use crate::graphics::damage; use crate::graphics::layer; use crate::graphics::text::{Editor, Paragraph, Text}; use crate::graphics::{self, Image}; @@ -20,39 +21,6 @@ pub struct Layer { pub images: Vec, } -#[derive(Debug, Clone)] -pub enum Item { - Live(T), - Group(Vec, Rectangle, Transformation), - Cached(Rc<[T]>, Rectangle, Transformation), -} - -impl Item { - pub fn transformation(&self) -> Transformation { - match self { - Item::Live(_) => Transformation::IDENTITY, - Item::Group(_, _, transformation) - | Item::Cached(_, _, transformation) => *transformation, - } - } - - pub fn clip_bounds(&self) -> Rectangle { - match self { - Item::Live(_) => Rectangle::INFINITE, - Item::Group(_, clip_bounds, _) - | Item::Cached(_, clip_bounds, _) => *clip_bounds, - } - } - - pub fn as_slice(&self) -> &[T] { - match self { - Item::Live(item) => std::slice::from_ref(item), - Item::Group(group, _, _) => group.as_slice(), - Item::Cached(cache, _, _) => cache, - } - } -} - impl Layer { pub fn draw_quad( &mut self, @@ -204,6 +172,81 @@ impl Layer { transformation, )); } + + pub fn damage(previous: &Self, current: &Self) -> Vec { + let mut damage = damage::list( + &previous.quads, + ¤t.quads, + |(quad, _)| vec![quad.bounds.expand(1.0)], + |(quad_a, background_a), (quad_b, background_b)| { + quad_a == quad_b && background_a == background_b + }, + ); + + let text = damage::diff( + &previous.text, + ¤t.text, + |item| { + item.as_slice() + .iter() + .filter_map(Text::visible_bounds) + .map(|bounds| bounds * item.transformation()) + .collect() + }, + |text_a, text_b| { + damage::list( + text_a.as_slice(), + text_b.as_slice(), + |text| { + text.visible_bounds() + .into_iter() + .filter_map(|bounds| { + bounds.intersection(&text_a.clip_bounds()) + }) + .collect() + }, + |text_a, text_b| text_a == text_b, + ) + }, + ); + + let primitives = damage::list( + &previous.primitives, + ¤t.primitives, + |item| match item { + Item::Live(primitive) => vec![primitive.visible_bounds()], + Item::Group(primitives, bounds, transformation) => { + damage::group( + primitives + .as_slice() + .iter() + .map(Primitive::visible_bounds) + .map(|bounds| bounds * *transformation) + .collect(), + *bounds, + ) + } + Item::Cached(_, bounds, _) => { + vec![*bounds] + } + }, + |primitive_a, primitive_b| match (primitive_a, primitive_b) { + ( + Item::Cached(cache_a, bounds_a, transformation_a), + Item::Cached(cache_b, bounds_b, transformation_b), + ) => { + Rc::ptr_eq(cache_a, cache_b) + && bounds_a == bounds_b + && transformation_a == transformation_b + } + _ => false, + }, + ); + + damage.extend(text); + damage.extend(primitives); + damage + } } impl Default for Layer { @@ -236,8 +279,41 @@ impl graphics::Layer for Layer { self.bounds = Rectangle::INFINITE; self.quads.clear(); - self.text.clear(); self.primitives.clear(); + self.text.clear(); self.images.clear(); } } + +#[derive(Debug, Clone)] +pub enum Item { + Live(T), + Group(Vec, Rectangle, Transformation), + Cached(Rc<[T]>, Rectangle, Transformation), +} + +impl Item { + pub fn transformation(&self) -> Transformation { + match self { + Item::Live(_) => Transformation::IDENTITY, + Item::Group(_, _, transformation) + | Item::Cached(_, _, transformation) => *transformation, + } + } + + pub fn clip_bounds(&self) -> Rectangle { + match self { + Item::Live(_) => Rectangle::INFINITE, + Item::Group(_, clip_bounds, _) + | Item::Cached(_, clip_bounds, _) => *clip_bounds, + } + } + + pub fn as_slice(&self) -> &[T] { + match self { + Item::Live(item) => std::slice::from_ref(item), + Item::Group(group, _, _) => group.as_slice(), + Item::Cached(cache, _, _) => cache, + } + } +} diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index fed2f32c..4c2c9430 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -29,7 +29,7 @@ pub use geometry::Geometry; use crate::core::renderer; use crate::core::{ - Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, + Background, Color, Font, Pixels, Point, Rectangle, Transformation, }; use crate::engine::Engine; use crate::graphics::compositor; @@ -58,9 +58,9 @@ impl Renderer { } } - pub fn layers(&mut self) -> impl Iterator { + pub fn layers(&mut self) -> &[Layer] { self.layers.flush(); - self.layers.iter() + self.layers.as_slice() } pub fn draw>( @@ -135,12 +135,12 @@ impl Renderer { ); for layer in self.layers.iter() { - let Some(clip_bounds) = region.intersection(&layer.bounds) + let Some(clip_bounds) = + region.intersection(&(layer.bounds * scale_factor)) else { continue; }; - let clip_bounds = clip_bounds * scale_factor; engine::adjust_clip_mask(clip_mask, clip_bounds); for (quad, background) in &layer.quads { @@ -154,30 +154,15 @@ impl Renderer { ); } - for group in &layer.text { - for text in group.as_slice() { - self.engine.draw_text( - text, - group.transformation() - * Transformation::scale(scale_factor), - pixels, - clip_mask, - clip_bounds, - ); - } - } - for group in &layer.primitives { - let Some(new_clip_bounds) = - group.clip_bounds().intersection(&layer.bounds) + let Some(new_clip_bounds) = (group.clip_bounds() + * scale_factor) + .intersection(&clip_bounds) else { continue; }; - engine::adjust_clip_mask( - clip_mask, - new_clip_bounds * scale_factor, - ); + engine::adjust_clip_mask(clip_mask, new_clip_bounds); for primitive in group.as_slice() { self.engine.draw_primitive( @@ -193,6 +178,19 @@ impl Renderer { engine::adjust_clip_mask(clip_mask, clip_bounds); } + for group in &layer.text { + for text in group.as_slice() { + self.engine.draw_text( + text, + group.transformation() + * Transformation::scale(scale_factor), + pixels, + clip_mask, + clip_bounds, + ); + } + } + for image in &layer.images { self.engine.draw_image( image, @@ -370,7 +368,7 @@ impl graphics::mesh::Renderer for Renderer { impl core::image::Renderer for Renderer { type Handle = core::image::Handle; - fn measure_image(&self, handle: &Self::Handle) -> Size { + fn measure_image(&self, handle: &Self::Handle) -> crate::core::Size { self.engine.raster_pipeline.dimensions(handle) } @@ -387,7 +385,10 @@ impl core::image::Renderer for Renderer { #[cfg(feature = "svg")] impl core::svg::Renderer for Renderer { - fn measure_svg(&self, handle: &core::svg::Handle) -> Size { + fn measure_svg( + &self, + handle: &core::svg::Handle, + ) -> crate::core::Size { self.engine.vector_pipeline.viewport_dimensions(handle) } diff --git a/tiny_skia/src/primitive.rs b/tiny_skia/src/primitive.rs index 5305788d..5de51047 100644 --- a/tiny_skia/src/primitive.rs +++ b/tiny_skia/src/primitive.rs @@ -1,3 +1,5 @@ +use crate::core::Rectangle; + #[derive(Debug, Clone, PartialEq)] pub enum Primitive { /// A path filled with some paint. @@ -19,3 +21,20 @@ pub enum Primitive { stroke: tiny_skia::Stroke, }, } + +impl Primitive { + /// Returns the visible bounds of the [`Primitive`]. + pub fn visible_bounds(&self) -> Rectangle { + let bounds = match self { + Primitive::Fill { path, .. } => path.bounds(), + Primitive::Stroke { path, .. } => path.bounds(), + }; + + Rectangle { + x: bounds.x(), + y: bounds.y(), + width: bounds.width(), + height: bounds.height(), + } + } +} diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index e1a9dfec..153af6d5 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -1,5 +1,6 @@ use crate::core::{Color, Rectangle, Size}; use crate::graphics::compositor::{self, Information}; +use crate::graphics::damage; use crate::graphics::error::{self, Error}; use crate::graphics::{self, Viewport}; use crate::{Layer, Renderer, Settings}; @@ -154,7 +155,7 @@ pub fn present>( .buffer_mut() .map_err(|_| compositor::SurfaceError::Lost)?; - let _last_layers = { + let last_layers = { let age = buffer.age(); surface.max_age = surface.max_age.max(age); @@ -167,26 +168,28 @@ pub fn present>( } }; - // TODO - // let damage = last_layers - // .and_then(|last_layers| { - // (surface.background_color == background_color) - // .then(|| damage::layers(last_layers, renderer.layers())) - // }) - // .unwrap_or_else(|| vec![Rectangle::with_size(viewport.logical_size())]); - - let damage = vec![Rectangle::with_size(viewport.logical_size())]; + let damage = last_layers + .and_then(|last_layers| { + (surface.background_color == background_color).then(|| { + damage::diff( + last_layers, + renderer.layers(), + |layer| vec![layer.bounds], + Layer::damage, + ) + }) + }) + .unwrap_or_else(|| vec![Rectangle::with_size(viewport.logical_size())]); if damage.is_empty() { return Ok(()); } - surface - .layer_stack - .push_front(renderer.layers().cloned().collect()); + surface.layer_stack.push_front(renderer.layers().to_vec()); surface.background_color = background_color; - // let damage = damage::group(damage, viewport.logical_size()); + let damage = + damage::group(damage, Rectangle::with_size(viewport.logical_size())); let mut pixels = tiny_skia::PixmapMut::from_bytes( bytemuck::cast_slice_mut(&mut buffer), -- cgit From 32cd456fb936117307c178b4d47ae89124c8329a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 10 Apr 2024 16:26:55 +0200 Subject: Account for `transformation` in `Text::visible_bounds` --- tiny_skia/src/layer.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/layer.rs b/tiny_skia/src/layer.rs index 0d770d81..17e6ac13 100644 --- a/tiny_skia/src/layer.rs +++ b/tiny_skia/src/layer.rs @@ -200,9 +200,7 @@ impl Layer { |text| { text.visible_bounds() .into_iter() - .filter_map(|bounds| { - bounds.intersection(&text_a.clip_bounds()) - }) + .map(|bounds| bounds * text_a.transformation()) .collect() }, |text_a, text_b| text_a == text_b, -- cgit From fdd9896dc5f727f4c659ad6252f1ab36cca77762 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 10 Apr 2024 19:55:27 +0200 Subject: Track image damage in `iced_tiny_skia` --- tiny_skia/src/layer.rs | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/layer.rs b/tiny_skia/src/layer.rs index 17e6ac13..d3d8b988 100644 --- a/tiny_skia/src/layer.rs +++ b/tiny_skia/src/layer.rs @@ -241,8 +241,16 @@ impl Layer { }, ); + let images = damage::list( + &previous.images, + ¤t.images, + |image| vec![image.bounds().expand(1.0)], + Image::eq, + ); + damage.extend(text); damage.extend(primitives); + damage.extend(images); damage } } -- cgit From 1e8554bf02f366b18b31b9ea1dfb150f7935ca06 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 10 Apr 2024 20:23:07 +0200 Subject: Sort damage by distance from origin in `iced_graphics::damage` --- tiny_skia/src/layer.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/layer.rs b/tiny_skia/src/layer.rs index d3d8b988..ec87c2bf 100644 --- a/tiny_skia/src/layer.rs +++ b/tiny_skia/src/layer.rs @@ -213,16 +213,14 @@ impl Layer { ¤t.primitives, |item| match item { Item::Live(primitive) => vec![primitive.visible_bounds()], - Item::Group(primitives, bounds, transformation) => { - damage::group( - primitives - .as_slice() - .iter() - .map(Primitive::visible_bounds) - .map(|bounds| bounds * *transformation) - .collect(), - *bounds, - ) + Item::Group(primitives, group_bounds, transformation) => { + primitives + .as_slice() + .iter() + .map(Primitive::visible_bounds) + .map(|bounds| bounds * *transformation) + .filter_map(|bounds| bounds.intersection(group_bounds)) + .collect() } Item::Cached(_, bounds, _) => { vec![*bounds] -- cgit From 43aafb7b79d51106c05f2494e6adc4a7a51d947e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 10 Apr 2024 20:31:44 +0200 Subject: Clip quad damage with layer bounds in `iced_tiny_skia` --- tiny_skia/src/layer.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/layer.rs b/tiny_skia/src/layer.rs index ec87c2bf..3e42e4aa 100644 --- a/tiny_skia/src/layer.rs +++ b/tiny_skia/src/layer.rs @@ -174,10 +174,20 @@ impl Layer { } pub fn damage(previous: &Self, current: &Self) -> Vec { + if previous.bounds != current.bounds { + return vec![previous.bounds, current.bounds]; + } + let mut damage = damage::list( &previous.quads, ¤t.quads, - |(quad, _)| vec![quad.bounds.expand(1.0)], + |(quad, _)| { + quad.bounds + .expand(1.0) + .intersection(¤t.bounds) + .into_iter() + .collect() + }, |(quad_a, background_a), (quad_b, background_b)| { quad_a == quad_b && background_a == background_b }, -- cgit From b5b78d505e22cafccb4ecbf57dc61f536ca558ca Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Apr 2024 07:57:54 +0200 Subject: Introduce `canvas::Cache` grouping Caches with the same `Group` will share their text atlas! --- tiny_skia/src/geometry.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 117daf41..02b6e1b9 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -1,9 +1,10 @@ use crate::core::text::LineHeight; use crate::core::{Pixels, Point, Radians, Rectangle, Size, Vector}; +use crate::graphics::cache::{self, Cached}; use crate::graphics::geometry::fill::{self, Fill}; use crate::graphics::geometry::stroke::{self, Stroke}; use crate::graphics::geometry::{self, Path, Style}; -use crate::graphics::{Cached, Gradient, Text}; +use crate::graphics::{Gradient, Text}; use crate::Primitive; use std::rc::Rc; @@ -32,7 +33,7 @@ impl Cached for Geometry { Self::Cache(cache.clone()) } - fn cache(self, _previous: Option) -> Cache { + fn cache(self, _group: cache::Group, _previous: Option) -> Cache { match self { Self::Live { primitives, -- cgit From 45254ab88c6ca76759523069c2fb8734de626f02 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 May 2024 00:55:49 +0200 Subject: Use `Bytes` as the `Container` of `ImageBuffer` Since we don't need to mutate images once loaded, we avoid unnecessary extra allocations. --- tiny_skia/src/raster.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs index 176b0da9..59f1e4d5 100644 --- a/tiny_skia/src/raster.rs +++ b/tiny_skia/src/raster.rs @@ -83,7 +83,7 @@ impl Cache { 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 image = graphics::image::load(handle).ok()?; let mut buffer = vec![0u32; image.width() as usize * image.height() as usize]; -- cgit From b52c7bb610f593fffc624d461dca17ac50c81626 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 May 2024 01:39:43 +0200 Subject: Use an opaque `Id` type for `image::Handle` Hashing pointers is a terrible idea. --- tiny_skia/src/raster.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs index 59f1e4d5..907fce7c 100644 --- a/tiny_skia/src/raster.rs +++ b/tiny_skia/src/raster.rs @@ -71,8 +71,8 @@ impl Pipeline { #[derive(Debug, Default)] struct Cache { - entries: FxHashMap>, - hits: FxHashSet, + entries: FxHashMap>, + hits: FxHashSet, } impl Cache { -- cgit From 09a6bcfffc24f5abdc8709403bab7ae1e01563f1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 2 May 2024 13:15:17 +0200 Subject: Add `Image` rotation support Co-authored-by: DKolter <68352124+DKolter@users.noreply.github.com> --- tiny_skia/src/engine.rs | 49 ++++++++++++++++++++++++++++++++++--------------- tiny_skia/src/layer.rs | 32 ++++++++++++++++++++++++++++---- tiny_skia/src/lib.rs | 17 ++++++++++++++--- tiny_skia/src/vector.rs | 4 +++- 4 files changed, 79 insertions(+), 23 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/engine.rs b/tiny_skia/src/engine.rs index fbca1274..564d3752 100644 --- a/tiny_skia/src/engine.rs +++ b/tiny_skia/src/engine.rs @@ -539,10 +539,10 @@ impl Engine { pub fn draw_image( &mut self, image: &Image, - _transformation: Transformation, - _pixels: &mut tiny_skia::PixmapMut<'_>, - _clip_mask: &mut tiny_skia::Mask, - _clip_bounds: Rectangle, + transformation: Transformation, + pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: &mut tiny_skia::Mask, + clip_bounds: Rectangle, ) { match image { #[cfg(feature = "image")] @@ -550,22 +550,31 @@ impl Engine { handle, filter_method, bounds, + rotation, + scale, } => { - let physical_bounds = *bounds * _transformation; + let physical_bounds = *bounds * transformation; - if !_clip_bounds.intersects(&physical_bounds) { + if !clip_bounds.intersects(&physical_bounds) { return; } - let clip_mask = (!physical_bounds.is_within(&_clip_bounds)) - .then_some(_clip_mask as &_); + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + + let center = physical_bounds.center(); + let transform = into_transform(transformation) + .post_rotate_at(rotation.to_degrees(), center.x, center.y) + .post_translate(-center.x, -center.y) + .post_scale(scale.width, scale.height) + .post_translate(center.x, center.y); self.raster_pipeline.draw( handle, *filter_method, *bounds, - _pixels, - into_transform(_transformation), + pixels, + transform, clip_mask, ); } @@ -574,21 +583,31 @@ impl Engine { handle, color, bounds, + rotation, + scale, } => { - let physical_bounds = *bounds * _transformation; + let physical_bounds = *bounds * transformation; - if !_clip_bounds.intersects(&physical_bounds) { + if !clip_bounds.intersects(&physical_bounds) { return; } - let clip_mask = (!physical_bounds.is_within(&_clip_bounds)) - .then_some(_clip_mask as &_); + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + + let center = physical_bounds.center(); + let transform = into_transform(transformation) + .post_rotate_at(rotation.to_degrees(), center.x, center.y) + .post_translate(-center.x, -center.y) + .post_scale(scale.width, scale.height) + .post_translate(center.x, center.y); self.vector_pipeline.draw( handle, *color, physical_bounds, - _pixels, + pixels, + transform, clip_mask, ); } diff --git a/tiny_skia/src/layer.rs b/tiny_skia/src/layer.rs index 3e42e4aa..e9651814 100644 --- a/tiny_skia/src/layer.rs +++ b/tiny_skia/src/layer.rs @@ -1,7 +1,7 @@ -use crate::core::image; -use crate::core::renderer::Quad; -use crate::core::svg; -use crate::core::{Background, Color, Point, Rectangle, Transformation}; +use crate::core::{ + image, renderer::Quad, svg, Background, Color, Point, Rectangle, Size, + Transformation, +}; use crate::graphics::damage; use crate::graphics::layer; use crate::graphics::text::{Editor, Paragraph, Text}; @@ -121,11 +121,15 @@ impl Layer { filter_method: image::FilterMethod, bounds: Rectangle, transformation: Transformation, + rotation: f32, + scale: Size, ) { let image = Image::Raster { handle, filter_method, bounds: bounds * transformation, + rotation, + scale, }; self.images.push(image); @@ -137,11 +141,15 @@ impl Layer { color: Option, bounds: Rectangle, transformation: Transformation, + rotation: f32, + scale: Size, ) { let svg = Image::Vector { handle, color, bounds: bounds * transformation, + rotation, + scale, }; self.images.push(svg); @@ -256,6 +264,22 @@ impl Layer { Image::eq, ); + // let center = bounds.center(); + // let rotated_size = RotationLayout::Change + // .apply_to_size(bounds.size(), *rotation); + // + // let scaled_size = Size::new( + // rotated_size.width * scale.width, + // rotated_size.height * scale.height, + // ); + // + // let top_left = Point::new( + // center.x - scaled_size.width / 2.0, + // center.y - scaled_size.height / 2.0, + // ); + // + // Rectangle::new(top_left, scaled_size).expand(1.0) + damage.extend(text); damage.extend(primitives); damage.extend(images); diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index 4c2c9430..4e3ebad3 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -29,7 +29,7 @@ pub use geometry::Geometry; use crate::core::renderer; use crate::core::{ - Background, Color, Font, Pixels, Point, Rectangle, Transformation, + Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, }; use crate::engine::Engine; use crate::graphics::compositor; @@ -377,9 +377,18 @@ impl core::image::Renderer for Renderer { handle: Self::Handle, filter_method: core::image::FilterMethod, bounds: Rectangle, + rotation: f32, + scale: Size, ) { let (layer, transformation) = self.layers.current_mut(); - layer.draw_image(handle, filter_method, bounds, transformation); + layer.draw_image( + handle, + filter_method, + bounds, + transformation, + rotation, + scale, + ); } } @@ -397,9 +406,11 @@ impl core::svg::Renderer for Renderer { handle: core::svg::Handle, color: Option, bounds: Rectangle, + rotation: f32, + scale: Size, ) { let (layer, transformation) = self.layers.current_mut(); - layer.draw_svg(handle, color, bounds, transformation); + layer.draw_svg(handle, color, bounds, transformation, rotation, scale); } } diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs index 5150cffe..8e3463f2 100644 --- a/tiny_skia/src/vector.rs +++ b/tiny_skia/src/vector.rs @@ -4,6 +4,7 @@ use crate::graphics::text; use resvg::usvg::{self, TreeTextToPath}; use rustc_hash::{FxHashMap, FxHashSet}; +use tiny_skia::Transform; use std::cell::RefCell; use std::collections::hash_map; @@ -34,6 +35,7 @@ impl Pipeline { color: Option, bounds: Rectangle, pixels: &mut tiny_skia::PixmapMut<'_>, + transform: Transform, clip_mask: Option<&tiny_skia::Mask>, ) { if let Some(image) = self.cache.borrow_mut().draw( @@ -46,7 +48,7 @@ impl Pipeline { bounds.y as i32, image, &tiny_skia::PixmapPaint::default(), - tiny_skia::Transform::identity(), + transform, clip_mask, ); } -- cgit From a57313b23ecb9843856ca0ea08635b6121fcb2cb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 2 May 2024 15:21:22 +0200 Subject: Simplify image rotation API and its internals --- tiny_skia/src/engine.rs | 26 ++++++++++++++------------ tiny_skia/src/layer.rs | 10 +++------- tiny_skia/src/lib.rs | 11 ++++------- 3 files changed, 21 insertions(+), 26 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/engine.rs b/tiny_skia/src/engine.rs index 564d3752..e9935bdb 100644 --- a/tiny_skia/src/engine.rs +++ b/tiny_skia/src/engine.rs @@ -551,7 +551,6 @@ impl Engine { filter_method, bounds, rotation, - scale, } => { let physical_bounds = *bounds * transformation; @@ -563,11 +562,13 @@ impl Engine { .then_some(clip_mask as &_); let center = physical_bounds.center(); - let transform = into_transform(transformation) - .post_rotate_at(rotation.to_degrees(), center.x, center.y) - .post_translate(-center.x, -center.y) - .post_scale(scale.width, scale.height) - .post_translate(center.x, center.y); + let radians = f32::from(*rotation); + + let transform = into_transform(transformation).post_rotate_at( + radians.to_degrees(), + center.x, + center.y, + ); self.raster_pipeline.draw( handle, @@ -584,7 +585,6 @@ impl Engine { color, bounds, rotation, - scale, } => { let physical_bounds = *bounds * transformation; @@ -596,11 +596,13 @@ impl Engine { .then_some(clip_mask as &_); let center = physical_bounds.center(); - let transform = into_transform(transformation) - .post_rotate_at(rotation.to_degrees(), center.x, center.y) - .post_translate(-center.x, -center.y) - .post_scale(scale.width, scale.height) - .post_translate(center.x, center.y); + let radians = f32::from(*rotation); + + let transform = into_transform(transformation).post_rotate_at( + radians.to_degrees(), + center.x, + center.y, + ); self.vector_pipeline.draw( handle, diff --git a/tiny_skia/src/layer.rs b/tiny_skia/src/layer.rs index e9651814..c8a31ba3 100644 --- a/tiny_skia/src/layer.rs +++ b/tiny_skia/src/layer.rs @@ -1,5 +1,5 @@ use crate::core::{ - image, renderer::Quad, svg, Background, Color, Point, Rectangle, Size, + image, renderer::Quad, svg, Background, Color, Point, Radians, Rectangle, Transformation, }; use crate::graphics::damage; @@ -121,15 +121,13 @@ impl Layer { filter_method: image::FilterMethod, bounds: Rectangle, transformation: Transformation, - rotation: f32, - scale: Size, + rotation: Radians, ) { let image = Image::Raster { handle, filter_method, bounds: bounds * transformation, rotation, - scale, }; self.images.push(image); @@ -141,15 +139,13 @@ impl Layer { color: Option, bounds: Rectangle, transformation: Transformation, - rotation: f32, - scale: Size, + rotation: Radians, ) { let svg = Image::Vector { handle, color, bounds: bounds * transformation, rotation, - scale, }; self.images.push(svg); diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index 4e3ebad3..75aaaf92 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -29,7 +29,7 @@ pub use geometry::Geometry; use crate::core::renderer; use crate::core::{ - Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, + Background, Color, Font, Pixels, Point, Radians, Rectangle, Transformation, }; use crate::engine::Engine; use crate::graphics::compositor; @@ -377,8 +377,7 @@ impl core::image::Renderer for Renderer { handle: Self::Handle, filter_method: core::image::FilterMethod, bounds: Rectangle, - rotation: f32, - scale: Size, + rotation: Radians, ) { let (layer, transformation) = self.layers.current_mut(); layer.draw_image( @@ -387,7 +386,6 @@ impl core::image::Renderer for Renderer { bounds, transformation, rotation, - scale, ); } } @@ -406,11 +404,10 @@ impl core::svg::Renderer for Renderer { handle: core::svg::Handle, color: Option, bounds: Rectangle, - rotation: f32, - scale: Size, + rotation: Radians, ) { let (layer, transformation) = self.layers.current_mut(); - layer.draw_svg(handle, color, bounds, transformation, rotation, scale); + layer.draw_svg(handle, color, bounds, transformation, rotation); } } -- cgit From 568ac66486937a294f2a79cefea277e4eb46b81e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 2 May 2024 17:15:26 +0200 Subject: Remove commented code in `tiny_skia::layer` --- tiny_skia/src/layer.rs | 16 ---------------- 1 file changed, 16 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/layer.rs b/tiny_skia/src/layer.rs index c8a31ba3..c907c93c 100644 --- a/tiny_skia/src/layer.rs +++ b/tiny_skia/src/layer.rs @@ -260,22 +260,6 @@ impl Layer { Image::eq, ); - // let center = bounds.center(); - // let rotated_size = RotationLayout::Change - // .apply_to_size(bounds.size(), *rotation); - // - // let scaled_size = Size::new( - // rotated_size.width * scale.width, - // rotated_size.height * scale.height, - // ); - // - // let top_left = Point::new( - // center.x - scaled_size.width / 2.0, - // center.y - scaled_size.height / 2.0, - // ); - // - // Rectangle::new(top_left, scaled_size).expand(1.0) - damage.extend(text); damage.extend(primitives); damage.extend(images); -- cgit From 15057a05c118dafcb8cf90d4119e66caaa6026c5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 3 May 2024 09:11:46 +0200 Subject: Introduce `center` widget helper ... and also make `center_x` and `center_y` set `width` and `height` to `Length::Fill`, respectively. This targets the most common use case when centering things and removes a bunch of boilerplate as a result. --- tiny_skia/src/engine.rs | 32 ++++++++++++++++---------------- tiny_skia/src/lib.rs | 6 +++--- 2 files changed, 19 insertions(+), 19 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/engine.rs b/tiny_skia/src/engine.rs index e9935bdb..544ff614 100644 --- a/tiny_skia/src/engine.rs +++ b/tiny_skia/src/engine.rs @@ -539,10 +539,10 @@ impl Engine { pub fn draw_image( &mut self, image: &Image, - transformation: Transformation, - pixels: &mut tiny_skia::PixmapMut<'_>, - clip_mask: &mut tiny_skia::Mask, - clip_bounds: Rectangle, + _transformation: Transformation, + _pixels: &mut tiny_skia::PixmapMut<'_>, + _clip_mask: &mut tiny_skia::Mask, + _clip_bounds: Rectangle, ) { match image { #[cfg(feature = "image")] @@ -552,19 +552,19 @@ impl Engine { bounds, rotation, } => { - let physical_bounds = *bounds * transformation; + let physical_bounds = *bounds * _transformation; - if !clip_bounds.intersects(&physical_bounds) { + if !_clip_bounds.intersects(&physical_bounds) { return; } - let clip_mask = (!physical_bounds.is_within(&clip_bounds)) - .then_some(clip_mask as &_); + let clip_mask = (!physical_bounds.is_within(&_clip_bounds)) + .then_some(_clip_mask as &_); let center = physical_bounds.center(); let radians = f32::from(*rotation); - let transform = into_transform(transformation).post_rotate_at( + let transform = into_transform(_transformation).post_rotate_at( radians.to_degrees(), center.x, center.y, @@ -574,7 +574,7 @@ impl Engine { handle, *filter_method, *bounds, - pixels, + _pixels, transform, clip_mask, ); @@ -586,19 +586,19 @@ impl Engine { bounds, rotation, } => { - let physical_bounds = *bounds * transformation; + let physical_bounds = *bounds * _transformation; - if !clip_bounds.intersects(&physical_bounds) { + if !_clip_bounds.intersects(&physical_bounds) { return; } - let clip_mask = (!physical_bounds.is_within(&clip_bounds)) - .then_some(clip_mask as &_); + let clip_mask = (!physical_bounds.is_within(&_clip_bounds)) + .then_some(_clip_mask as &_); let center = physical_bounds.center(); let radians = f32::from(*rotation); - let transform = into_transform(transformation).post_rotate_at( + let transform = into_transform(_transformation).post_rotate_at( radians.to_degrees(), center.x, center.y, @@ -608,7 +608,7 @@ impl Engine { handle, *color, physical_bounds, - pixels, + _pixels, transform, clip_mask, ); diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index 75aaaf92..e0cbfa0d 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -29,7 +29,7 @@ pub use geometry::Geometry; use crate::core::renderer; use crate::core::{ - Background, Color, Font, Pixels, Point, Radians, Rectangle, Transformation, + Background, Color, Font, Pixels, Point, Rectangle, Transformation, }; use crate::engine::Engine; use crate::graphics::compositor; @@ -377,7 +377,7 @@ impl core::image::Renderer for Renderer { handle: Self::Handle, filter_method: core::image::FilterMethod, bounds: Rectangle, - rotation: Radians, + rotation: core::Radians, ) { let (layer, transformation) = self.layers.current_mut(); layer.draw_image( @@ -404,7 +404,7 @@ impl core::svg::Renderer for Renderer { handle: core::svg::Handle, color: Option, bounds: Rectangle, - rotation: Radians, + rotation: core::Radians, ) { let (layer, transformation) = self.layers.current_mut(); layer.draw_svg(handle, color, bounds, transformation, rotation); -- cgit From fa9e1d96ea1924b51749b775ea0e67e69bc8a305 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 3 May 2024 13:25:58 +0200 Subject: Introduce dynamic `opacity` support for `Image` and `Svg` --- tiny_skia/src/engine.rs | 4 ++++ tiny_skia/src/layer.rs | 4 ++++ tiny_skia/src/lib.rs | 12 +++++++++++- tiny_skia/src/raster.rs | 2 ++ tiny_skia/src/vector.rs | 6 +++++- 5 files changed, 26 insertions(+), 2 deletions(-) (limited to 'tiny_skia/src') diff --git a/tiny_skia/src/engine.rs b/tiny_skia/src/engine.rs index 544ff614..028b304f 100644 --- a/tiny_skia/src/engine.rs +++ b/tiny_skia/src/engine.rs @@ -551,6 +551,7 @@ impl Engine { filter_method, bounds, rotation, + opacity, } => { let physical_bounds = *bounds * _transformation; @@ -574,6 +575,7 @@ impl Engine { handle, *filter_method, *bounds, + *opacity, _pixels, transform, clip_mask, @@ -585,6 +587,7 @@ impl Engine { color, bounds, rotation, + opacity, } => { let physical_bounds = *bounds * _transformation; @@ -608,6 +611,7 @@ impl Engine { handle, *color, physical_bounds, + *opacity, _pixels, transform, clip_mask, diff --git a/tiny_skia/src/layer.rs b/tiny_skia/src/layer.rs index c907c93c..48fca1d8 100644 --- a/tiny_skia/src/layer.rs +++ b/tiny_skia/src/layer.rs @@ -122,12 +122,14 @@ impl Layer { bounds: Rectangle, transformation: Transformation, rotation: Radians, + opacity: f32, ) { let image = Image::Raster { handle, filter_method, bounds: bounds * transformation, rotation, + opacity, }; self.images.push(image); @@ -140,12 +142,14 @@ impl Layer { bounds: Rectangle, transformation: Transformation, rotation: Radians, + opacity: f32, ) { let svg = Image::Vector { handle, color, bounds: bounds * transformation, rotation, + opacity, }; self.images.push(svg); diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index e0cbfa0d..1aabff00 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -378,6 +378,7 @@ impl core::image::Renderer for Renderer { filter_method: core::image::FilterMethod, bounds: Rectangle, rotation: core::Radians, + opacity: f32, ) { let (layer, transformation) = self.layers.current_mut(); layer.draw_image( @@ -386,6 +387,7 @@ impl core::image::Renderer for Renderer { bounds, transformation, rotation, + opacity, ); } } @@ -405,9 +407,17 @@ impl core::svg::Renderer for Renderer { color: Option, bounds: Rectangle, rotation: core::Radians, + opacity: f32, ) { let (layer, transformation) = self.layers.current_mut(); - layer.draw_svg(handle, color, bounds, transformation, rotation); + layer.draw_svg( + handle, + color, + bounds, + transformation, + rotation, + opacity, + ); } } diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs index 907fce7c..c40f55b2 100644 --- a/tiny_skia/src/raster.rs +++ b/tiny_skia/src/raster.rs @@ -31,6 +31,7 @@ impl Pipeline { handle: &raster::Handle, filter_method: raster::FilterMethod, bounds: Rectangle, + opacity: f32, pixels: &mut tiny_skia::PixmapMut<'_>, transform: tiny_skia::Transform, clip_mask: Option<&tiny_skia::Mask>, @@ -56,6 +57,7 @@ impl Pipeline { image, &tiny_skia::PixmapPaint { quality, + opacity, ..Default::default() }, transform, diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs index 8e3463f2..bbe08cb8 100644 --- a/tiny_skia/src/vector.rs +++ b/tiny_skia/src/vector.rs @@ -34,6 +34,7 @@ impl Pipeline { handle: &Handle, color: Option, bounds: Rectangle, + opacity: f32, pixels: &mut tiny_skia::PixmapMut<'_>, transform: Transform, clip_mask: Option<&tiny_skia::Mask>, @@ -47,7 +48,10 @@ impl Pipeline { bounds.x as i32, bounds.y as i32, image, - &tiny_skia::PixmapPaint::default(), + &tiny_skia::PixmapPaint { + opacity, + ..tiny_skia::PixmapPaint::default() + }, transform, clip_mask, ); -- cgit