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; +        }      }  }  | 
