diff options
-rw-r--r-- | core/src/rectangle.rs | 14 | ||||
-rw-r--r-- | graphics/src/primitive.rs | 4 | ||||
-rw-r--r-- | tiny_skia/src/backend.rs | 275 | ||||
-rw-r--r-- | tiny_skia/src/text.rs | 16 |
4 files changed, 200 insertions, 109 deletions
diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index 5cdcbe78..7ff324cb 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -66,6 +66,11 @@ impl Rectangle<f32> { Size::new(self.width, self.height) } + /// Returns the area of the [`Rectangle`]. + pub fn area(&self) -> f32 { + self.width * self.height + } + /// Returns true if the given [`Point`] is contained in the [`Rectangle`]. pub fn contains(&self, point: Point) -> bool { self.x <= point.x @@ -74,6 +79,15 @@ impl Rectangle<f32> { && point.y <= self.y + self.height } + /// Returns true if the current [`Rectangle`] is completely within the given + /// `container`. + pub fn is_within(&self, container: &Rectangle) -> bool { + container.contains(self.position()) + && container.contains( + self.position() + Vector::new(self.width, self.height), + ) + } + /// Computes the intersection with the given [`Rectangle`]. pub fn intersection( &self, diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index f079ff6f..01546dcb 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -178,8 +178,8 @@ impl Primitive { } Self::Quad { bounds, .. } | Self::Image { bounds, .. } - | Self::Svg { bounds, .. } - | Self::Clip { bounds, .. } => bounds.expand(1.0), + | Self::Svg { bounds, .. } => bounds.expand(1.0), + Self::Clip { bounds, .. } => *bounds, Self::SolidMesh { size, .. } | Self::GradientMesh { size, .. } => { Rectangle::with_size(*size) } diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 8101082a..b1417409 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -49,7 +49,7 @@ impl Backend { primitives: &[Primitive], viewport: &Viewport, background_color: Color, - _overlay: &[T], + overlay: &[T], ) { let physical_size = viewport.physical_size(); @@ -70,37 +70,20 @@ impl Backend { self.last_size = physical_size; let scale_factor = viewport.scale_factor() as f32; - let physical_bounds = Rectangle { - x: 0.0, - y: 0.0, - width: physical_size.width as f32, - height: physical_size.height as f32, - }; - - dbg!(damage.len()); - 'draw_regions: for (i, region) in damage.iter().enumerate() { - for previous in damage.iter().take(i) { - if previous.contains(region.position()) - && previous.contains( - region.position() - + Vector::new(region.width, region.height), - ) - { - continue 'draw_regions; - } - } + let damage = group_damage(damage, scale_factor, physical_size); - let region = *region * scale_factor; - - let Some(region) = physical_bounds.intersection(®ion) else { continue }; + if !overlay.is_empty() { + pixels.fill(into_color(background_color)); + } + for region in damage { let path = tiny_skia::PathBuilder::from_rect( tiny_skia::Rect::from_xywh( region.x, region.y, - region.width.min(viewport.physical_width() as f32), - region.height.min(viewport.physical_height() as f32), + region.width, + region.height, ) .expect("Create damage rectangle"), ); @@ -111,6 +94,7 @@ impl Backend { shader: tiny_skia::Shader::SolidColor(into_color( background_color, )), + anti_alias: false, ..Default::default() }, tiny_skia::FillRule::default(), @@ -131,49 +115,62 @@ impl Backend { ); } - //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: true, - // ..tiny_skia::Paint::default() - // }, - // &tiny_skia::Stroke { - // width: 1.0, - // ..tiny_skia::Stroke::default() - // }, - // tiny_skia::Transform::identity(), - // None, - //); + 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, + ); + } } - //for (i, text) in overlay.iter().enumerate() { - // const OVERLAY_TEXT_SIZE: f32 = 20.0; - - // self.draw_primitive( - // &Primitive::Text { - // content: text.as_ref().to_owned(), - // size: OVERLAY_TEXT_SIZE, - // bounds: Rectangle { - // x: 10.0, - // y: 10.0 + i as f32 * OVERLAY_TEXT_SIZE * 1.2, - // width: f32::INFINITY, - // height: f32::INFINITY, - // }, - // color: Color::BLACK, - // font: Font::MONOSPACE, - // horizontal_alignment: alignment::Horizontal::Left, - // vertical_alignment: alignment::Vertical::Top, - // }, - // pixels, - // clip_mask, - // Rectangle::EMPTY, - // scale_factor, - // Vector::ZERO, - // ); - //} + if !overlay.is_empty() { + let bounds = Rectangle { + x: 0.0, + y: 0.0, + width: viewport.physical_width() as f32, + height: viewport.physical_height() as f32, + }; + + adjust_clip_mask(clip_mask, pixels, bounds); + + for (i, text) in overlay.iter().enumerate() { + const OVERLAY_TEXT_SIZE: f32 = 20.0; + + self.draw_primitive( + &Primitive::Text { + content: text.as_ref().to_owned(), + size: OVERLAY_TEXT_SIZE, + bounds: Rectangle { + x: 10.0, + y: 10.0 + i as f32 * OVERLAY_TEXT_SIZE * 1.2, + width: bounds.width - 1.0, + height: bounds.height - 1.0, + }, + color: Color::BLACK, + font: Font::MONOSPACE, + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + }, + pixels, + clip_mask, + bounds, + scale_factor, + Vector::ZERO, + ); + } + } self.text_pipeline.trim_cache(); @@ -201,12 +198,15 @@ impl Backend { border_width, border_color, } => { - if !clip_bounds - .intersects(&((*bounds + translation) * scale_factor)) - { + let physical_bounds = (*bounds + translation) * scale_factor; + + if !clip_bounds.intersects(&physical_bounds) { return; } + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then(|| clip_mask as &_); + let transform = tiny_skia::Transform::from_translate( translation.x, translation.y, @@ -230,7 +230,7 @@ impl Backend { }, tiny_skia::FillRule::EvenOdd, transform, - Some(clip_mask), + clip_mask, ); if *border_width > 0.0 { @@ -248,7 +248,7 @@ impl Backend { ..tiny_skia::Stroke::default() }, transform, - Some(clip_mask), + clip_mask, ); } } @@ -261,12 +261,16 @@ impl Backend { horizontal_alignment, vertical_alignment, } => { - if !clip_bounds.intersects( - &((primitive.bounds() + translation) * scale_factor), - ) { + let physical_bounds = + (primitive.bounds() + translation) * scale_factor; + + if !clip_bounds.intersects(&physical_bounds) { return; } + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then(|| clip_mask as &_); + self.text_pipeline.draw( content, (*bounds + translation) * scale_factor, @@ -276,7 +280,7 @@ impl Backend { *horizontal_alignment, *vertical_alignment, pixels, - Some(clip_mask), + clip_mask, ); } #[cfg(feature = "image")] @@ -323,18 +327,21 @@ impl Backend { } => { let bounds = path.bounds(); - if !clip_bounds.intersects( - &((Rectangle { - x: bounds.x(), - y: bounds.y(), - width: bounds.width(), - height: bounds.height(), - } + translation) - * scale_factor), - ) { + let physical_bounds = (Rectangle { + x: bounds.x(), + y: bounds.y(), + width: bounds.width(), + height: bounds.height(), + } + translation) + * scale_factor; + + if !clip_bounds.intersects(&physical_bounds) { return; } + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then(|| clip_mask as &_); + pixels.fill_path( path, paint, @@ -342,7 +349,7 @@ impl Backend { transform .post_translate(translation.x, translation.y) .post_scale(scale_factor, scale_factor), - Some(clip_mask), + clip_mask, ); } Primitive::Stroke { @@ -353,18 +360,21 @@ impl Backend { } => { let bounds = path.bounds(); - if !clip_bounds.intersects( - &((Rectangle { - x: bounds.x(), - y: bounds.y(), - width: bounds.width(), - height: bounds.height(), - } + translation) - * scale_factor), - ) { + let physical_bounds = (Rectangle { + x: bounds.x(), + y: bounds.y(), + width: bounds.width(), + height: bounds.height(), + } + translation) + * scale_factor; + + if !clip_bounds.intersects(&physical_bounds) { return; } + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then(|| clip_mask as &_); + pixels.stroke_path( path, paint, @@ -372,7 +382,7 @@ impl Backend { transform .post_translate(translation.x, translation.y) .post_scale(scale_factor, scale_factor), - Some(clip_mask), + clip_mask, ); } Primitive::Group { primitives } => { @@ -403,15 +413,26 @@ impl Backend { Primitive::Clip { bounds, content } => { let bounds = (*bounds + translation) * scale_factor; - 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() - { - return; - } + if bounds == clip_bounds { + self.draw_primitive( + content, + pixels, + clip_mask, + bounds, + scale_factor, + translation, + ); + } 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; + } - if let Some(bounds) = clip_bounds.intersection(&bounds) { adjust_clip_mask(clip_mask, pixels, bounds); self.draw_primitive( @@ -614,11 +635,57 @@ fn adjust_clip_mask( pixels.height(), &path, tiny_skia::FillRule::EvenOdd, - true, + false, ) .expect("Set path of clipping area"); } +fn group_damage( + mut damage: Vec<Rectangle>, + scale_factor: f32, + bounds: Size<u32>, +) -> Vec<Rectangle> { + use std::cmp::Ordering; + + const AREA_THRESHOLD: f32 = 20_000.0; + + let bounds = Rectangle { + x: 0.0, + y: 0.0, + width: bounds.width as f32, + height: bounds.height as f32, + }; + + damage.sort_by(|a, b| { + a.x.partial_cmp(&b.x) + .unwrap_or(Ordering::Equal) + .then_with(|| a.y.partial_cmp(&b.y).unwrap_or(Ordering::Equal)) + }); + + let mut output = Vec::new(); + let mut scaled = damage + .into_iter() + .filter_map(|region| (region * scale_factor).intersection(&bounds)) + .filter(|region| region.width >= 1.0 && region.height >= 1.0); + + if let Some(mut current) = scaled.next() { + for region in scaled { + let union = current.union(®ion); + + if union.area() - current.area() - region.area() <= AREA_THRESHOLD { + current = union; + } else { + output.push(current); + current = region; + } + } + + output.push(current); + } + + output +} + impl iced_graphics::Backend for Backend { fn trim_measurements(&mut self) { self.text_pipeline.trim_measurement_cache(); diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 865132b4..512503e0 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -336,6 +336,7 @@ struct Cache { entries: FxHashMap<KeyHash, cosmic_text::Buffer>, recently_used: FxHashSet<KeyHash>, hasher: HashBuilder, + trim_count: usize, } #[cfg(not(target_arch = "wasm32"))] @@ -345,11 +346,14 @@ type HashBuilder = twox_hash::RandomXxHashBuilder64; type HashBuilder = std::hash::BuildHasherDefault<twox_hash::XxHash64>; impl Cache { + const TRIM_INTERVAL: usize = 300; + fn new() -> Self { Self { entries: FxHashMap::default(), recently_used: FxHashSet::default(), hasher: HashBuilder::default(), + trim_count: 0, } } @@ -404,10 +408,16 @@ impl Cache { } fn trim(&mut self) { - self.entries - .retain(|key, _| self.recently_used.contains(key)); + if self.trim_count > Self::TRIM_INTERVAL { + self.entries + .retain(|key, _| self.recently_used.contains(key)); - self.recently_used.clear(); + self.recently_used.clear(); + + self.trim_count = 0; + } else { + self.trim_count += 1; + } } } |