summaryrefslogtreecommitdiffstats
path: root/tiny_skia
diff options
context:
space:
mode:
Diffstat (limited to 'tiny_skia')
-rw-r--r--tiny_skia/src/backend.rs361
-rw-r--r--tiny_skia/src/primitive.rs82
-rw-r--r--tiny_skia/src/text.rs16
-rw-r--r--tiny_skia/src/window/compositor.rs14
4 files changed, 318 insertions, 155 deletions
diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs
index 58076b84..974faa74 100644
--- a/tiny_skia/src/backend.rs
+++ b/tiny_skia/src/backend.rs
@@ -17,6 +17,10 @@ pub struct Backend {
#[cfg(feature = "svg")]
vector_pipeline: crate::vector::Pipeline,
+
+ last_primitives: Vec<Primitive>,
+ last_background_color: Color,
+ last_size: Size<u32>,
}
impl Backend {
@@ -31,6 +35,10 @@ impl Backend {
#[cfg(feature = "svg")]
vector_pipeline: crate::vector::Pipeline::new(),
+
+ last_primitives: Vec::new(),
+ last_background_color: Color::BLACK,
+ last_size: Size::new(0, 0),
}
}
@@ -42,48 +50,151 @@ impl Backend {
viewport: &Viewport,
background_color: Color,
overlay: &[T],
- ) {
- pixels.fill(into_color(background_color));
+ ) -> bool {
+ let physical_size = viewport.physical_size();
+
+ let damage = if self.last_background_color == background_color
+ && self.last_size == physical_size
+ {
+ Primitive::damage_list(&self.last_primitives, primitives)
+ } else {
+ vec![Rectangle::with_size(viewport.logical_size())]
+ };
+
+ if damage.is_empty() {
+ return false;
+ }
+
+ self.last_primitives = primitives.to_vec();
+ self.last_background_color = background_color;
+ self.last_size = physical_size;
let scale_factor = viewport.scale_factor() as f32;
- for primitive in primitives {
- self.draw_primitive(
- primitive,
- pixels,
- clip_mask,
+ let damage = group_damage(damage, scale_factor, physical_size);
+
+ 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,
- scale_factor,
- Vector::ZERO,
);
}
- 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,
+ for region 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,
+ ..Default::default()
},
- pixels,
- clip_mask,
+ tiny_skia::FillRule::default(),
+ tiny_skia::Transform::identity(),
None,
- scale_factor,
- Vector::ZERO,
);
+
+ adjust_clip_mask(clip_mask, pixels, region);
+
+ for primitive in primitives {
+ self.draw_primitive(
+ primitive,
+ pixels,
+ clip_mask,
+ region,
+ scale_factor,
+ Vector::ZERO,
+ );
+ }
+
+ 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,
+ );
+ }
}
+ //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();
#[cfg(feature = "image")]
@@ -91,6 +202,8 @@ impl Backend {
#[cfg(feature = "svg")]
self.vector_pipeline.trim_cache();
+
+ true
}
fn draw_primitive(
@@ -98,7 +211,7 @@ impl Backend {
primitive: &Primitive,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: &mut tiny_skia::ClipMask,
- clip_bounds: Option<Rectangle>,
+ clip_bounds: Rectangle,
scale_factor: f32,
translation: Vector,
) {
@@ -110,6 +223,15 @@ impl Backend {
border_width,
border_color,
} => {
+ let physical_bounds = (*bounds + translation) * 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 = tiny_skia::Transform::from_translate(
translation.x,
translation.y,
@@ -117,7 +239,6 @@ impl Backend {
.post_scale(scale_factor, scale_factor);
let path = rounded_rectangle(*bounds, *border_radius);
- let clip_mask = clip_bounds.map(|_| clip_mask as &_);
pixels.fill_path(
&path,
@@ -165,6 +286,16 @@ impl Backend {
horizontal_alignment,
vertical_alignment,
} => {
+ 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_some(clip_mask as &_);
+
self.text_pipeline.draw(
content,
(*bounds + translation) * scale_factor,
@@ -174,24 +305,28 @@ impl Backend {
*horizontal_alignment,
*vertical_alignment,
pixels,
- clip_bounds.map(|_| clip_mask as &_),
+ clip_mask,
);
}
#[cfg(feature = "image")]
Primitive::Image { handle, bounds } => {
+ let physical_bounds = (*bounds + translation) * 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 = tiny_skia::Transform::from_translate(
translation.x,
translation.y,
)
.post_scale(scale_factor, scale_factor);
- self.raster_pipeline.draw(
- handle,
- *bounds,
- pixels,
- transform,
- clip_bounds.map(|_| clip_mask as &_),
- );
+ self.raster_pipeline
+ .draw(handle, *bounds, pixels, transform, clip_mask);
}
#[cfg(feature = "svg")]
Primitive::Svg {
@@ -199,12 +334,21 @@ impl Backend {
bounds,
color,
} => {
+ let physical_bounds = (*bounds + translation) * 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 + translation) * scale_factor,
pixels,
- clip_bounds.map(|_| clip_mask as &_),
+ clip_mask,
);
}
Primitive::Fill {
@@ -213,6 +357,23 @@ impl Backend {
rule,
transform,
} => {
+ let bounds = path.bounds();
+
+ 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_some(clip_mask as &_);
+
pixels.fill_path(
path,
paint,
@@ -220,7 +381,7 @@ impl Backend {
transform
.post_translate(translation.x, translation.y)
.post_scale(scale_factor, scale_factor),
- clip_bounds.map(|_| clip_mask as &_),
+ clip_mask,
);
}
Primitive::Stroke {
@@ -229,6 +390,23 @@ impl Backend {
stroke,
transform,
} => {
+ let bounds = path.bounds();
+
+ 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_some(clip_mask as &_);
+
pixels.stroke_path(
path,
paint,
@@ -236,7 +414,7 @@ impl Backend {
transform
.post_translate(translation.x, translation.y)
.post_scale(scale_factor, scale_factor),
- clip_bounds.map(|_| clip_mask as &_),
+ clip_mask,
);
}
Primitive::Group { primitives } => {
@@ -267,29 +445,38 @@ 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;
+ }
- adjust_clip_mask(clip_mask, pixels, bounds);
+ adjust_clip_mask(clip_mask, pixels, bounds);
- self.draw_primitive(
- content,
- pixels,
- clip_mask,
- Some(bounds),
- scale_factor,
- translation,
- );
+ self.draw_primitive(
+ content,
+ pixels,
+ clip_mask,
+ bounds,
+ scale_factor,
+ translation,
+ );
- if let Some(bounds) = clip_bounds {
- adjust_clip_mask(clip_mask, pixels, bounds);
- } else {
- clip_mask.clear();
+ adjust_clip_mask(clip_mask, pixels, clip_bounds);
}
}
Primitive::Cache { content } => {
@@ -480,11 +667,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(&region);
+
+ 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/primitive.rs b/tiny_skia/src/primitive.rs
deleted file mode 100644
index 22daaedc..00000000
--- a/tiny_skia/src/primitive.rs
+++ /dev/null
@@ -1,82 +0,0 @@
-use crate::{Rectangle, Vector};
-
-use std::sync::Arc;
-
-#[derive(Debug, Clone)]
-pub enum Primitive {
- /// A group of primitives
- Group {
- /// The primitives of the group
- primitives: Vec<Primitive>,
- },
- /// A clip primitive
- Clip {
- /// The bounds of the clip
- bounds: Rectangle,
- /// The content of the clip
- content: Box<Primitive>,
- },
- /// A primitive that applies a translation
- Translate {
- /// The translation vector
- translation: Vector,
-
- /// The primitive to translate
- content: Box<Primitive>,
- },
- /// A cached primitive.
- ///
- /// This can be useful if you are implementing a widget where primitive
- /// generation is expensive.
- Cached {
- /// The cached primitive
- cache: Arc<Primitive>,
- },
- /// A basic primitive.
- Basic(iced_graphics::Primitive),
-}
-
-impl iced_graphics::backend::Primitive for Primitive {
- fn translate(self, translation: Vector) -> Self {
- Self::Translate {
- translation,
- content: Box::new(self),
- }
- }
-
- fn clip(self, bounds: Rectangle) -> Self {
- Self::Clip {
- bounds,
- content: Box::new(self),
- }
- }
-}
-
-#[derive(Debug, Clone, Default)]
-pub struct Recording(pub(crate) Vec<Primitive>);
-
-impl iced_graphics::backend::Recording for Recording {
- type Primitive = Primitive;
-
- fn push(&mut self, primitive: Primitive) {
- self.0.push(primitive);
- }
-
- fn push_basic(&mut self, basic: iced_graphics::Primitive) {
- self.0.push(Primitive::Basic(basic));
- }
-
- fn group(self) -> Self::Primitive {
- Primitive::Group { primitives: self.0 }
- }
-
- fn clear(&mut self) {
- self.0.clear();
- }
-}
-
-impl Recording {
- pub fn primitives(&self) -> &[Primitive] {
- &self.0
- }
-}
diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs
index e0e893bd..f5994d09 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,
}
}
@@ -397,10 +401,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;
+ }
}
}
diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs
index cea1cabf..6e4bb6ef 100644
--- a/tiny_skia/src/window/compositor.rs
+++ b/tiny_skia/src/window/compositor.rs
@@ -106,7 +106,7 @@ pub fn present<Theme, T: AsRef<str>>(
) -> Result<(), compositor::SurfaceError> {
let physical_size = viewport.physical_size();
- backend.draw(
+ let drawn = backend.draw(
&mut tiny_skia::PixmapMut::from_bytes(
bytemuck::cast_slice_mut(&mut surface.buffer),
physical_size.width,
@@ -120,11 +120,13 @@ pub fn present<Theme, T: AsRef<str>>(
overlay,
);
- surface.window.set_buffer(
- &surface.buffer,
- physical_size.width as u16,
- physical_size.height as u16,
- );
+ if drawn {
+ surface.window.set_buffer(
+ &surface.buffer,
+ physical_size.width as u16,
+ physical_size.height as u16,
+ );
+ }
Ok(())
}