diff options
Diffstat (limited to 'tiny_skia/src/backend.rs')
-rw-r--r-- | tiny_skia/src/backend.rs | 361 |
1 files changed, 297 insertions, 64 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(®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(); |