diff options
Diffstat (limited to 'tiny_skia/src')
| -rw-r--r-- | tiny_skia/src/backend.rs | 278 | ||||
| -rw-r--r-- | tiny_skia/src/primitive.rs | 82 | ||||
| -rw-r--r-- | tiny_skia/src/raster.rs | 2 | ||||
| -rw-r--r-- | tiny_skia/src/text.rs | 18 | ||||
| -rw-r--r-- | tiny_skia/src/vector.rs | 8 | ||||
| -rw-r--r-- | tiny_skia/src/window/compositor.rs | 56 | 
6 files changed, 259 insertions, 185 deletions
| diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 58076b84..9c69e1d2 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -1,4 +1,3 @@ -use crate::core::alignment;  use crate::core::text;  use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector};  use crate::graphics::backend; @@ -37,51 +36,99 @@ impl Backend {      pub fn draw<T: AsRef<str>>(          &mut self,          pixels: &mut tiny_skia::PixmapMut<'_>, -        clip_mask: &mut tiny_skia::ClipMask, +        clip_mask: &mut tiny_skia::Mask,          primitives: &[Primitive],          viewport: &Viewport, +        damage: &[Rectangle],          background_color: Color,          overlay: &[T],      ) { -        pixels.fill(into_color(background_color)); - +        let physical_size = viewport.physical_size();          let scale_factor = viewport.scale_factor() as f32; -        for primitive in primitives { -            self.draw_primitive( -                primitive, -                pixels, -                clip_mask, +        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 ®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, +                    ..Default::default()                  }, -                pixels, -                clip_mask, +                tiny_skia::FillRule::default(), +                tiny_skia::Transform::identity(),                  None, -                scale_factor, -                Vector::ZERO,              ); + +            adjust_clip_mask(clip_mask, 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, +                ); +            }          }          self.text_pipeline.trim_cache(); @@ -97,8 +144,8 @@ impl Backend {          &mut self,          primitive: &Primitive,          pixels: &mut tiny_skia::PixmapMut<'_>, -        clip_mask: &mut tiny_skia::ClipMask, -        clip_bounds: Option<Rectangle>, +        clip_mask: &mut tiny_skia::Mask, +        clip_bounds: Rectangle,          scale_factor: f32,          translation: Vector,      ) { @@ -110,6 +157,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 +173,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 +220,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 +239,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 +268,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 +291,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 +315,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 +324,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 +348,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 +379,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; -                } - -                adjust_clip_mask(clip_mask, pixels, bounds); +                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, 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, clip_bounds);                  }              }              Primitive::Cache { content } => { @@ -462,11 +583,9 @@ fn arc_to(      }  } -fn adjust_clip_mask( -    clip_mask: &mut tiny_skia::ClipMask, -    pixels: &tiny_skia::PixmapMut<'_>, -    bounds: Rectangle, -) { +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(bounds.x, bounds.y, bounds.width, bounds.height); @@ -474,15 +593,12 @@ fn adjust_clip_mask(          builder.finish().unwrap()      }; -    clip_mask -        .set_path( -            pixels.width(), -            pixels.height(), -            &path, -            tiny_skia::FillRule::EvenOdd, -            true, -        ) -        .expect("Set path of clipping area"); +    clip_mask.fill_path( +        &path, +        tiny_skia::FillRule::EvenOdd, +        false, +        tiny_skia::Transform::default(), +    );  }  impl iced_graphics::Backend for Backend { 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/raster.rs b/tiny_skia/src/raster.rs index 2fd73f8c..3887ec8d 100644 --- a/tiny_skia/src/raster.rs +++ b/tiny_skia/src/raster.rs @@ -31,7 +31,7 @@ impl Pipeline {          bounds: Rectangle,          pixels: &mut tiny_skia::PixmapMut<'_>,          transform: tiny_skia::Transform, -        clip_mask: Option<&tiny_skia::ClipMask>, +        clip_mask: Option<&tiny_skia::Mask>,      ) {          if let Some(image) = self.cache.borrow_mut().allocate(handle) {              let width_scale = bounds.width / image.width() as f32; diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index e0e893bd..1246bbd5 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -50,7 +50,7 @@ impl Pipeline {          horizontal_alignment: alignment::Horizontal,          vertical_alignment: alignment::Vertical,          pixels: &mut tiny_skia::PixmapMut<'_>, -        clip_mask: Option<&tiny_skia::ClipMask>, +        clip_mask: Option<&tiny_skia::Mask>,      ) {          let font_system = self.font_system.get_mut();          let key = Key { @@ -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/vector.rs b/tiny_skia/src/vector.rs index 8509b761..fc411fdd 100644 --- a/tiny_skia/src/vector.rs +++ b/tiny_skia/src/vector.rs @@ -32,7 +32,7 @@ impl Pipeline {          color: Option<Color>,          bounds: Rectangle,          pixels: &mut tiny_skia::PixmapMut<'_>, -        clip_mask: Option<&tiny_skia::ClipMask>, +        clip_mask: Option<&tiny_skia::Mask>,      ) {          if let Some(image) = self.cache.borrow_mut().draw(              handle, @@ -72,6 +72,8 @@ struct RasterKey {  impl Cache {      fn load(&mut self, handle: &Handle) -> Option<&usvg::Tree> { +        use usvg::TreeParsing; +          let id = handle.id();          if let hash_map::Entry::Vacant(entry) = self.trees.entry(id) { @@ -131,9 +133,9 @@ impl Cache {              resvg::render(                  tree,                  if size.width > size.height { -                    usvg::FitTo::Width(size.width) +                    resvg::FitTo::Width(size.width)                  } else { -                    usvg::FitTo::Height(size.height) +                    resvg::FitTo::Height(size.height)                  },                  tiny_skia::Transform::default(),                  image.as_mut(), diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index cea1cabf..9999a188 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -1,5 +1,6 @@ -use crate::core::Color; +use crate::core::{Color, Rectangle};  use crate::graphics::compositor::{self, Information, SurfaceError}; +use crate::graphics::damage;  use crate::graphics::{Error, Primitive, Viewport};  use crate::{Backend, Renderer, Settings}; @@ -7,13 +8,15 @@ use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};  use std::marker::PhantomData;  pub struct Compositor<Theme> { -    clip_mask: tiny_skia::ClipMask,      _theme: PhantomData<Theme>,  }  pub struct Surface {      window: softbuffer::GraphicsContext,      buffer: Vec<u32>, +    clip_mask: tiny_skia::Mask, +    primitives: Option<Vec<Primitive>>, +    background_color: Color,  }  impl<Theme> crate::graphics::Compositor for Compositor<Theme> { @@ -43,6 +46,10 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {          Surface {              window,              buffer: vec![0; width as usize * height as usize], +            clip_mask: tiny_skia::Mask::new(width, height) +                .expect("Create clip mask"), +            primitives: None, +            background_color: Color::BLACK,          }      } @@ -53,6 +60,9 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {          height: u32,      ) {          surface.buffer.resize((width * height) as usize, 0); +        surface.clip_mask = +            tiny_skia::Mask::new(width, height).expect("Create clip mask"); +        surface.primitives = None;      }      fn fetch_information(&self) -> Information { @@ -72,7 +82,6 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {      ) -> Result<(), SurfaceError> {          renderer.with_primitives(|backend, primitives| {              present( -                self,                  backend,                  surface,                  primitives, @@ -85,18 +94,15 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {  }  pub fn new<Theme>(settings: Settings) -> (Compositor<Theme>, Backend) { -    // TOD      (          Compositor { -            clip_mask: tiny_skia::ClipMask::new(),              _theme: PhantomData,          },          Backend::new(settings),      )  } -pub fn present<Theme, T: AsRef<str>>( -    compositor: &mut Compositor<Theme>, +pub fn present<T: AsRef<str>>(      backend: &mut Backend,      surface: &mut Surface,      primitives: &[Primitive], @@ -105,17 +111,39 @@ pub fn present<Theme, T: AsRef<str>>(      overlay: &[T],  ) -> Result<(), compositor::SurfaceError> {      let physical_size = viewport.physical_size(); +    let scale_factor = viewport.scale_factor() as f32; + +    let mut pixels = tiny_skia::PixmapMut::from_bytes( +        bytemuck::cast_slice_mut(&mut surface.buffer), +        physical_size.width, +        physical_size.height, +    ) +    .expect("Create pixel map"); + +    let damage = surface +        .primitives +        .as_deref() +        .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())]); + +    if damage.is_empty() { +        return Ok(()); +    } + +    surface.primitives = Some(primitives.to_vec()); +    surface.background_color = background_color; + +    let damage = damage::group(damage, scale_factor, physical_size);      backend.draw( -        &mut tiny_skia::PixmapMut::from_bytes( -            bytemuck::cast_slice_mut(&mut surface.buffer), -            physical_size.width, -            physical_size.height, -        ) -        .expect("Create pixel map"), -        &mut compositor.clip_mask, +        &mut pixels, +        &mut surface.clip_mask,          primitives,          viewport, +        &damage,          background_color,          overlay,      ); | 
