Port iced_tiny_skia to new layering architecture

This commit is contained in:
Héctor Ramón Jiménez 2024-04-09 22:25:16 +02:00
parent 2c6fd9ac14
commit 6ad5bb3597
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
28 changed files with 1948 additions and 1935 deletions

File diff suppressed because it is too large Load diff

831
tiny_skia/src/engine.rs Normal file
View file

@ -0,0 +1,831 @@
use crate::core::renderer::Quad;
use crate::core::{
Background, Color, Gradient, Rectangle, Size, Transformation, Vector,
};
use crate::graphics::{Image, Text};
use crate::text;
use crate::Primitive;
#[derive(Debug)]
pub struct Engine {
text_pipeline: text::Pipeline,
#[cfg(feature = "image")]
pub(crate) raster_pipeline: crate::raster::Pipeline,
#[cfg(feature = "svg")]
pub(crate) vector_pipeline: crate::vector::Pipeline,
}
impl Engine {
pub fn new() -> Self {
Self {
text_pipeline: text::Pipeline::new(),
#[cfg(feature = "image")]
raster_pipeline: crate::raster::Pipeline::new(),
#[cfg(feature = "svg")]
vector_pipeline: crate::vector::Pipeline::new(),
}
}
pub fn draw_quad(
&mut self,
quad: &Quad,
background: &Background,
transformation: Transformation,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: &mut tiny_skia::Mask,
clip_bounds: Rectangle,
) {
debug_assert!(
quad.bounds.width.is_normal(),
"Quad with non-normal width!"
);
debug_assert!(
quad.bounds.height.is_normal(),
"Quad with non-normal height!"
);
let physical_bounds = quad.bounds * transformation;
if !clip_bounds.intersects(&physical_bounds) {
return;
}
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
.then_some(clip_mask as &_);
let transform = into_transform(transformation);
// Make sure the border radius is not larger than the bounds
let border_width = quad
.border
.width
.min(quad.bounds.width / 2.0)
.min(quad.bounds.height / 2.0);
let mut fill_border_radius = <[f32; 4]>::from(quad.border.radius);
for radius in &mut fill_border_radius {
*radius = (*radius)
.min(quad.bounds.width / 2.0)
.min(quad.bounds.height / 2.0);
}
let path = rounded_rectangle(quad.bounds, fill_border_radius);
let shadow = quad.shadow;
if shadow.color.a > 0.0 {
let shadow_bounds = Rectangle {
x: quad.bounds.x + shadow.offset.x - shadow.blur_radius,
y: quad.bounds.y + shadow.offset.y - shadow.blur_radius,
width: quad.bounds.width + shadow.blur_radius * 2.0,
height: quad.bounds.height + shadow.blur_radius * 2.0,
} * transformation;
let radii = fill_border_radius
.into_iter()
.map(|radius| radius * transformation.scale_factor())
.collect::<Vec<_>>();
let (x, y, width, height) = (
shadow_bounds.x as u32,
shadow_bounds.y as u32,
shadow_bounds.width as u32,
shadow_bounds.height as u32,
);
let half_width = physical_bounds.width / 2.0;
let half_height = physical_bounds.height / 2.0;
let colors = (y..y + height)
.flat_map(|y| (x..x + width).map(move |x| (x as f32, y as f32)))
.filter_map(|(x, y)| {
tiny_skia::Size::from_wh(half_width, half_height).map(
|size| {
let shadow_distance = rounded_box_sdf(
Vector::new(
x - physical_bounds.position().x
- (shadow.offset.x
* transformation.scale_factor())
- half_width,
y - physical_bounds.position().y
- (shadow.offset.y
* transformation.scale_factor())
- half_height,
),
size,
&radii,
)
.max(0.0);
let shadow_alpha = 1.0
- smoothstep(
-shadow.blur_radius
* transformation.scale_factor(),
shadow.blur_radius
* transformation.scale_factor(),
shadow_distance,
);
let mut color = into_color(shadow.color);
color.apply_opacity(shadow_alpha);
color.to_color_u8().premultiply()
},
)
})
.collect();
if let Some(pixmap) = tiny_skia::IntSize::from_wh(width, height)
.and_then(|size| {
tiny_skia::Pixmap::from_vec(
bytemuck::cast_vec(colors),
size,
)
})
{
pixels.draw_pixmap(
x as i32,
y as i32,
pixmap.as_ref(),
&tiny_skia::PixmapPaint::default(),
tiny_skia::Transform::default(),
None,
);
}
}
pixels.fill_path(
&path,
&tiny_skia::Paint {
shader: match background {
Background::Color(color) => {
tiny_skia::Shader::SolidColor(into_color(*color))
}
Background::Gradient(Gradient::Linear(linear)) => {
let (start, end) =
linear.angle.to_distance(&quad.bounds);
let stops: Vec<tiny_skia::GradientStop> = linear
.stops
.into_iter()
.flatten()
.map(|stop| {
tiny_skia::GradientStop::new(
stop.offset,
tiny_skia::Color::from_rgba(
stop.color.b,
stop.color.g,
stop.color.r,
stop.color.a,
)
.expect("Create color"),
)
})
.collect();
tiny_skia::LinearGradient::new(
tiny_skia::Point {
x: start.x,
y: start.y,
},
tiny_skia::Point { x: end.x, y: end.y },
if stops.is_empty() {
vec![tiny_skia::GradientStop::new(
0.0,
tiny_skia::Color::BLACK,
)]
} else {
stops
},
tiny_skia::SpreadMode::Pad,
tiny_skia::Transform::identity(),
)
.expect("Create linear gradient")
}
},
anti_alias: true,
..tiny_skia::Paint::default()
},
tiny_skia::FillRule::EvenOdd,
transform,
clip_mask,
);
if border_width > 0.0 {
// Border path is offset by half the border width
let border_bounds = Rectangle {
x: quad.bounds.x + border_width / 2.0,
y: quad.bounds.y + border_width / 2.0,
width: quad.bounds.width - border_width,
height: quad.bounds.height - border_width,
};
// Make sure the border radius is correct
let mut border_radius = <[f32; 4]>::from(quad.border.radius);
let mut is_simple_border = true;
for radius in &mut border_radius {
*radius = if *radius == 0.0 {
// Path should handle this fine
0.0
} else if *radius > border_width / 2.0 {
*radius - border_width / 2.0
} else {
is_simple_border = false;
0.0
}
.min(border_bounds.width / 2.0)
.min(border_bounds.height / 2.0);
}
// Stroking a path works well in this case
if is_simple_border {
let border_path =
rounded_rectangle(border_bounds, border_radius);
pixels.stroke_path(
&border_path,
&tiny_skia::Paint {
shader: tiny_skia::Shader::SolidColor(into_color(
quad.border.color,
)),
anti_alias: true,
..tiny_skia::Paint::default()
},
&tiny_skia::Stroke {
width: border_width,
..tiny_skia::Stroke::default()
},
transform,
clip_mask,
);
} else {
// Draw corners that have too small border radii as having no border radius,
// but mask them with the rounded rectangle with the correct border radius.
let mut temp_pixmap = tiny_skia::Pixmap::new(
quad.bounds.width as u32,
quad.bounds.height as u32,
)
.unwrap();
let mut quad_mask = tiny_skia::Mask::new(
quad.bounds.width as u32,
quad.bounds.height as u32,
)
.unwrap();
let zero_bounds = Rectangle {
x: 0.0,
y: 0.0,
width: quad.bounds.width,
height: quad.bounds.height,
};
let path = rounded_rectangle(zero_bounds, fill_border_radius);
quad_mask.fill_path(
&path,
tiny_skia::FillRule::EvenOdd,
true,
transform,
);
let path_bounds = Rectangle {
x: border_width / 2.0,
y: border_width / 2.0,
width: quad.bounds.width - border_width,
height: quad.bounds.height - border_width,
};
let border_radius_path =
rounded_rectangle(path_bounds, border_radius);
temp_pixmap.stroke_path(
&border_radius_path,
&tiny_skia::Paint {
shader: tiny_skia::Shader::SolidColor(into_color(
quad.border.color,
)),
anti_alias: true,
..tiny_skia::Paint::default()
},
&tiny_skia::Stroke {
width: border_width,
..tiny_skia::Stroke::default()
},
transform,
Some(&quad_mask),
);
pixels.draw_pixmap(
quad.bounds.x as i32,
quad.bounds.y as i32,
temp_pixmap.as_ref(),
&tiny_skia::PixmapPaint::default(),
transform,
clip_mask,
);
}
}
}
pub fn draw_text(
&mut self,
text: &Text,
transformation: Transformation,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: &mut tiny_skia::Mask,
clip_bounds: Rectangle,
) {
match text {
Text::Paragraph {
paragraph,
position,
color,
clip_bounds: _, // TODO
transformation: local_transformation,
} => {
let transformation = transformation * *local_transformation;
let physical_bounds =
Rectangle::new(*position, paragraph.min_bounds)
* transformation;
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_paragraph(
paragraph,
*position,
*color,
pixels,
clip_mask,
transformation,
);
}
Text::Editor {
editor,
position,
color,
clip_bounds: _, // TODO
transformation: local_transformation,
} => {
let transformation = transformation * *local_transformation;
let physical_bounds =
Rectangle::new(*position, editor.bounds) * transformation;
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_editor(
editor,
*position,
*color,
pixels,
clip_mask,
transformation,
);
}
Text::Cached {
content,
bounds,
color,
size,
line_height,
font,
horizontal_alignment,
vertical_alignment,
shaping,
clip_bounds: _, // TODO
} => {
let physical_bounds = *bounds * transformation;
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_cached(
content,
*bounds,
*color,
*size,
*line_height,
*font,
*horizontal_alignment,
*vertical_alignment,
*shaping,
pixels,
clip_mask,
transformation,
);
}
Text::Raw {
raw,
transformation: local_transformation,
} => {
let Some(buffer) = raw.buffer.upgrade() else {
return;
};
let transformation = transformation * *local_transformation;
let (width, height) = buffer.size();
let physical_bounds =
Rectangle::new(raw.position, Size::new(width, height))
* transformation;
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_raw(
&buffer,
raw.position,
raw.color,
pixels,
clip_mask,
transformation,
);
}
}
}
pub fn draw_primitive(
&mut self,
primitive: &Primitive,
transformation: Transformation,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: &mut tiny_skia::Mask,
layer_bounds: Rectangle,
) {
match primitive {
Primitive::Fill { path, paint, rule } => {
let physical_bounds = {
let bounds = path.bounds();
Rectangle {
x: bounds.x(),
y: bounds.y(),
width: bounds.width(),
height: bounds.height(),
} * transformation
};
let Some(clip_bounds) =
layer_bounds.intersection(&physical_bounds)
else {
return;
};
let clip_mask =
(physical_bounds != clip_bounds).then_some(clip_mask as &_);
pixels.fill_path(
path,
paint,
*rule,
into_transform(transformation),
clip_mask,
);
}
Primitive::Stroke {
path,
paint,
stroke,
} => {
let physical_bounds = {
let bounds = path.bounds();
Rectangle {
x: bounds.x(),
y: bounds.y(),
width: bounds.width(),
height: bounds.height(),
} * transformation
};
let Some(clip_bounds) =
layer_bounds.intersection(&physical_bounds)
else {
return;
};
let clip_mask =
(physical_bounds != clip_bounds).then_some(clip_mask as &_);
pixels.stroke_path(
path,
paint,
stroke,
into_transform(transformation),
clip_mask,
);
}
}
}
pub fn draw_image(
&mut self,
image: &Image,
transformation: Transformation,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: &mut tiny_skia::Mask,
clip_bounds: Rectangle,
) {
match image {
#[cfg(feature = "image")]
Image::Raster {
handle,
filter_method,
bounds,
} => {
let physical_bounds = *bounds * transformation;
if !clip_bounds.intersects(&physical_bounds) {
return;
}
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
.then_some(clip_mask as &_);
self.raster_pipeline.draw(
handle,
*filter_method,
*bounds,
pixels,
into_transform(transformation),
clip_mask,
);
}
#[cfg(feature = "svg")]
Image::Vector {
handle,
color,
bounds,
} => {
let physical_bounds = *bounds * transformation;
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,
physical_bounds,
pixels,
clip_mask,
);
}
#[cfg(not(feature = "image"))]
Image::Raster { .. } => {
log::warn!(
"Unsupported primitive in `iced_tiny_skia`: {image:?}",
);
}
#[cfg(not(feature = "svg"))]
Image::Vector { .. } => {
log::warn!(
"Unsupported primitive in `iced_tiny_skia`: {image:?}",
);
}
}
}
pub fn trim(&mut self) {
self.text_pipeline.trim_cache();
#[cfg(feature = "image")]
self.raster_pipeline.trim_cache();
#[cfg(feature = "svg")]
self.vector_pipeline.trim_cache();
}
}
pub fn into_color(color: Color) -> tiny_skia::Color {
tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a)
.expect("Convert color from iced to tiny_skia")
}
fn into_transform(transformation: Transformation) -> tiny_skia::Transform {
let translation = transformation.translation();
tiny_skia::Transform {
sx: transformation.scale_factor(),
kx: 0.0,
ky: 0.0,
sy: transformation.scale_factor(),
tx: translation.x,
ty: translation.y,
}
}
fn rounded_rectangle(
bounds: Rectangle,
border_radius: [f32; 4],
) -> tiny_skia::Path {
let [top_left, top_right, bottom_right, bottom_left] = border_radius;
if top_left == 0.0
&& top_right == 0.0
&& bottom_right == 0.0
&& bottom_left == 0.0
{
return tiny_skia::PathBuilder::from_rect(
tiny_skia::Rect::from_xywh(
bounds.x,
bounds.y,
bounds.width,
bounds.height,
)
.expect("Build quad rectangle"),
);
}
if top_left == top_right
&& top_left == bottom_right
&& top_left == bottom_left
&& top_left == bounds.width / 2.0
&& top_left == bounds.height / 2.0
{
return tiny_skia::PathBuilder::from_circle(
bounds.x + bounds.width / 2.0,
bounds.y + bounds.height / 2.0,
top_left,
)
.expect("Build circle path");
}
let mut builder = tiny_skia::PathBuilder::new();
builder.move_to(bounds.x + top_left, bounds.y);
builder.line_to(bounds.x + bounds.width - top_right, bounds.y);
if top_right > 0.0 {
arc_to(
&mut builder,
bounds.x + bounds.width - top_right,
bounds.y,
bounds.x + bounds.width,
bounds.y + top_right,
top_right,
);
}
maybe_line_to(
&mut builder,
bounds.x + bounds.width,
bounds.y + bounds.height - bottom_right,
);
if bottom_right > 0.0 {
arc_to(
&mut builder,
bounds.x + bounds.width,
bounds.y + bounds.height - bottom_right,
bounds.x + bounds.width - bottom_right,
bounds.y + bounds.height,
bottom_right,
);
}
maybe_line_to(
&mut builder,
bounds.x + bottom_left,
bounds.y + bounds.height,
);
if bottom_left > 0.0 {
arc_to(
&mut builder,
bounds.x + bottom_left,
bounds.y + bounds.height,
bounds.x,
bounds.y + bounds.height - bottom_left,
bottom_left,
);
}
maybe_line_to(&mut builder, bounds.x, bounds.y + top_left);
if top_left > 0.0 {
arc_to(
&mut builder,
bounds.x,
bounds.y + top_left,
bounds.x + top_left,
bounds.y,
top_left,
);
}
builder.finish().expect("Build rounded rectangle path")
}
fn maybe_line_to(path: &mut tiny_skia::PathBuilder, x: f32, y: f32) {
if path.last_point() != Some(tiny_skia::Point { x, y }) {
path.line_to(x, y);
}
}
fn arc_to(
path: &mut tiny_skia::PathBuilder,
x_from: f32,
y_from: f32,
x_to: f32,
y_to: f32,
radius: f32,
) {
let svg_arc = kurbo::SvgArc {
from: kurbo::Point::new(f64::from(x_from), f64::from(y_from)),
to: kurbo::Point::new(f64::from(x_to), f64::from(y_to)),
radii: kurbo::Vec2::new(f64::from(radius), f64::from(radius)),
x_rotation: 0.0,
large_arc: false,
sweep: true,
};
match kurbo::Arc::from_svg_arc(&svg_arc) {
Some(arc) => {
arc.to_cubic_beziers(0.1, |p1, p2, p| {
path.cubic_to(
p1.x as f32,
p1.y as f32,
p2.x as f32,
p2.y as f32,
p.x as f32,
p.y as f32,
);
});
}
None => {
path.line_to(x_to, y_to);
}
}
}
fn smoothstep(a: f32, b: f32, x: f32) -> f32 {
let x = ((x - a) / (b - a)).clamp(0.0, 1.0);
x * x * (3.0 - 2.0 * x)
}
fn rounded_box_sdf(
to_center: Vector,
size: tiny_skia::Size,
radii: &[f32],
) -> f32 {
let radius = match (to_center.x > 0.0, to_center.y > 0.0) {
(true, true) => radii[2],
(true, false) => radii[1],
(false, true) => radii[3],
(false, false) => radii[0],
};
let x = (to_center.x.abs() - size.width() + radius).max(0.0);
let y = (to_center.y.abs() - size.height() + radius).max(0.0);
(x.powf(2.0) + y.powf(2.0)).sqrt() - radius
}
pub 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(
tiny_skia::Rect::from_xywh(
bounds.x,
bounds.y,
bounds.width,
bounds.height,
)
.unwrap(),
);
builder.finish().unwrap()
};
clip_mask.fill_path(
&path,
tiny_skia::FillRule::EvenOdd,
false,
tiny_skia::Transform::default(),
);
}

View file

@ -1,58 +1,98 @@
use crate::core::text::LineHeight;
use crate::core::{
Pixels, Point, Radians, Rectangle, Size, Transformation, Vector,
};
use crate::core::{Pixels, Point, Radians, Rectangle, Size, Vector};
use crate::graphics::geometry::fill::{self, Fill};
use crate::graphics::geometry::stroke::{self, Stroke};
use crate::graphics::geometry::{self, Path, Style, Text};
use crate::graphics::Gradient;
use crate::primitive::{self, Primitive};
use crate::graphics::geometry::{self, Path, Style};
use crate::graphics::{Cached, Gradient, Text};
use crate::Primitive;
use std::rc::Rc;
#[derive(Debug)]
pub enum Geometry {
Live {
text: Vec<Text>,
primitives: Vec<Primitive>,
clip_bounds: Rectangle,
},
Cache(Cache),
}
#[derive(Debug, Clone)]
pub struct Cache {
pub text: Rc<[Text]>,
pub primitives: Rc<[Primitive]>,
pub clip_bounds: Rectangle,
}
impl Cached for Geometry {
type Cache = Cache;
fn load(cache: &Cache) -> Self {
Self::Cache(cache.clone())
}
fn cache(self, _previous: Option<Cache>) -> Cache {
match self {
Self::Live {
primitives,
text,
clip_bounds,
} => Cache {
primitives: Rc::from(primitives),
text: Rc::from(text),
clip_bounds,
},
Self::Cache(cache) => cache,
}
}
}
#[derive(Debug)]
pub struct Frame {
size: Size,
clip_bounds: Rectangle,
transform: tiny_skia::Transform,
stack: Vec<tiny_skia::Transform>,
primitives: Vec<Primitive>,
text: Vec<Text>,
}
impl Frame {
pub fn new(size: Size) -> Self {
Self {
size,
transform: tiny_skia::Transform::identity(),
stack: Vec::new(),
primitives: Vec::new(),
}
Self::with_clip(Rectangle::with_size(size))
}
pub fn into_primitive(self) -> Primitive {
Primitive::Clip {
bounds: Rectangle::new(Point::ORIGIN, self.size),
content: Box::new(Primitive::Group {
primitives: self.primitives,
}),
pub fn with_clip(clip_bounds: Rectangle) -> Self {
Self {
clip_bounds,
stack: Vec::new(),
primitives: Vec::new(),
text: Vec::new(),
transform: tiny_skia::Transform::from_translate(
clip_bounds.x,
clip_bounds.y,
),
}
}
}
impl geometry::frame::Backend for Frame {
type Geometry = Primitive;
type Geometry = Geometry;
fn width(&self) -> f32 {
self.size.width
self.clip_bounds.width
}
fn height(&self) -> f32 {
self.size.height
self.clip_bounds.height
}
fn size(&self) -> Size {
self.size
self.clip_bounds.size()
}
fn center(&self) -> Point {
Point::new(self.size.width / 2.0, self.size.height / 2.0)
Point::new(self.clip_bounds.width / 2.0, self.clip_bounds.height / 2.0)
}
fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
@ -67,12 +107,11 @@ impl geometry::frame::Backend for Frame {
let mut paint = into_paint(fill.style);
paint.shader.transform(self.transform);
self.primitives
.push(Primitive::Custom(primitive::Custom::Fill {
path,
paint,
rule: into_fill_rule(fill.rule),
}));
self.primitives.push(Primitive::Fill {
path,
paint,
rule: into_fill_rule(fill.rule),
});
}
fn fill_rectangle(
@ -95,12 +134,11 @@ impl geometry::frame::Backend for Frame {
};
paint.shader.transform(self.transform);
self.primitives
.push(Primitive::Custom(primitive::Custom::Fill {
path,
paint,
rule: into_fill_rule(fill.rule),
}));
self.primitives.push(Primitive::Fill {
path,
paint,
rule: into_fill_rule(fill.rule),
});
}
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
@ -116,15 +154,14 @@ impl geometry::frame::Backend for Frame {
let mut paint = into_paint(stroke.style);
paint.shader.transform(self.transform);
self.primitives
.push(Primitive::Custom(primitive::Custom::Stroke {
path,
paint,
stroke: skia_stroke,
}));
self.primitives.push(Primitive::Stroke {
path,
paint,
stroke: skia_stroke,
});
}
fn fill_text(&mut self, text: impl Into<Text>) {
fn fill_text(&mut self, text: impl Into<geometry::Text>) {
let text = text.into();
let (scale_x, scale_y) = self.transform.get_scale();
@ -171,12 +208,12 @@ impl geometry::frame::Backend for Frame {
};
// TODO: Honor layering!
self.primitives.push(Primitive::Text {
self.text.push(Text::Cached {
content: text.content,
bounds,
color: text.color,
size,
line_height,
line_height: line_height.to_absolute(size),
font: text.font,
horizontal_alignment: text.horizontal_alignment,
vertical_alignment: text.vertical_alignment,
@ -197,14 +234,12 @@ impl geometry::frame::Backend for Frame {
}
fn draft(&mut self, clip_bounds: Rectangle) -> Self {
Self::new(clip_bounds.size())
Self::with_clip(clip_bounds)
}
fn paste(&mut self, frame: Self, at: Point) {
self.primitives.push(Primitive::Transform {
transformation: Transformation::translate(at.x, at.y),
content: Box::new(frame.into_primitive()),
});
fn paste(&mut self, frame: Self, _at: Point) {
self.primitives.extend(frame.primitives);
self.text.extend(frame.text);
}
fn translate(&mut self, translation: Vector) {
@ -230,8 +265,12 @@ impl geometry::frame::Backend for Frame {
self.transform = self.transform.pre_scale(scale.x, scale.y);
}
fn into_geometry(self) -> Self::Geometry {
self.into_primitive()
fn into_geometry(self) -> Geometry {
Geometry::Live {
primitives: self.primitives,
text: self.text,
clip_bounds: self.clip_bounds,
}
}
}

243
tiny_skia/src/layer.rs Normal file
View file

@ -0,0 +1,243 @@
use crate::core::image;
use crate::core::renderer::Quad;
use crate::core::svg;
use crate::core::{Background, Color, Point, Rectangle, Transformation};
use crate::graphics::layer;
use crate::graphics::text::{Editor, Paragraph, Text};
use crate::graphics::{self, Image};
use crate::Primitive;
use std::rc::Rc;
pub type Stack = layer::Stack<Layer>;
#[derive(Debug, Clone)]
pub struct Layer {
pub bounds: Rectangle,
pub quads: Vec<(Quad, Background)>,
pub primitives: Vec<Item<Primitive>>,
pub text: Vec<Item<Text>>,
pub images: Vec<Image>,
}
#[derive(Debug, Clone)]
pub enum Item<T> {
Live(T),
Group(Vec<T>, Rectangle, Transformation),
Cached(Rc<[T]>, Rectangle, Transformation),
}
impl<T> Item<T> {
pub fn transformation(&self) -> Transformation {
match self {
Item::Live(_) => Transformation::IDENTITY,
Item::Group(_, _, transformation)
| Item::Cached(_, _, transformation) => *transformation,
}
}
pub fn clip_bounds(&self) -> Rectangle {
match self {
Item::Live(_) => Rectangle::INFINITE,
Item::Group(_, clip_bounds, _)
| Item::Cached(_, clip_bounds, _) => *clip_bounds,
}
}
pub fn as_slice(&self) -> &[T] {
match self {
Item::Live(item) => std::slice::from_ref(item),
Item::Group(group, _, _) => group.as_slice(),
Item::Cached(cache, _, _) => cache,
}
}
}
impl Layer {
pub fn draw_quad(
&mut self,
mut quad: Quad,
background: Background,
transformation: Transformation,
) {
quad.bounds = quad.bounds * transformation;
self.quads.push((quad, background));
}
pub fn draw_paragraph(
&mut self,
paragraph: &Paragraph,
position: Point,
color: Color,
clip_bounds: Rectangle,
transformation: Transformation,
) {
let paragraph = Text::Paragraph {
paragraph: paragraph.downgrade(),
position,
color,
clip_bounds,
transformation,
};
self.text.push(Item::Live(paragraph));
}
pub fn draw_editor(
&mut self,
editor: &Editor,
position: Point,
color: Color,
clip_bounds: Rectangle,
transformation: Transformation,
) {
let editor = Text::Editor {
editor: editor.downgrade(),
position,
color,
clip_bounds,
transformation,
};
self.text.push(Item::Live(editor));
}
pub fn draw_text(
&mut self,
text: crate::core::Text,
position: Point,
color: Color,
clip_bounds: Rectangle,
transformation: Transformation,
) {
let text = Text::Cached {
content: text.content,
bounds: Rectangle::new(position, text.bounds) * transformation,
color,
size: text.size * transformation.scale_factor(),
line_height: text.line_height.to_absolute(text.size)
* transformation.scale_factor(),
font: text.font,
horizontal_alignment: text.horizontal_alignment,
vertical_alignment: text.vertical_alignment,
shaping: text.shaping,
clip_bounds: clip_bounds * transformation,
};
self.text.push(Item::Live(text));
}
pub fn draw_text_group(
&mut self,
text: Vec<Text>,
clip_bounds: Rectangle,
transformation: Transformation,
) {
self.text
.push(Item::Group(text, clip_bounds, transformation));
}
pub fn draw_text_cache(
&mut self,
text: Rc<[Text]>,
clip_bounds: Rectangle,
transformation: Transformation,
) {
self.text
.push(Item::Cached(text, clip_bounds, transformation));
}
pub fn draw_image(
&mut self,
handle: image::Handle,
filter_method: image::FilterMethod,
bounds: Rectangle,
transformation: Transformation,
) {
let image = Image::Raster {
handle,
filter_method,
bounds: bounds * transformation,
};
self.images.push(image);
}
pub fn draw_svg(
&mut self,
handle: svg::Handle,
color: Option<Color>,
bounds: Rectangle,
transformation: Transformation,
) {
let svg = Image::Vector {
handle,
color,
bounds: bounds * transformation,
};
self.images.push(svg);
}
pub fn draw_primitive_group(
&mut self,
primitives: Vec<Primitive>,
clip_bounds: Rectangle,
transformation: Transformation,
) {
self.primitives.push(Item::Group(
primitives,
clip_bounds,
transformation,
));
}
pub fn draw_primitive_cache(
&mut self,
primitives: Rc<[Primitive]>,
clip_bounds: Rectangle,
transformation: Transformation,
) {
self.primitives.push(Item::Cached(
primitives,
clip_bounds,
transformation,
));
}
}
impl Default for Layer {
fn default() -> Self {
Self {
bounds: Rectangle::INFINITE,
quads: Vec::new(),
primitives: Vec::new(),
text: Vec::new(),
images: Vec::new(),
}
}
}
impl graphics::Layer for Layer {
fn with_bounds(bounds: Rectangle) -> Self {
Self {
bounds,
..Self::default()
}
}
fn flush(&mut self) {}
fn resize(&mut self, bounds: graphics::core::Rectangle) {
self.bounds = bounds;
}
fn reset(&mut self) {
self.bounds = Rectangle::INFINITE;
self.quads.clear();
self.text.clear();
self.primitives.clear();
self.images.clear();
}
}

View file

@ -2,7 +2,8 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
pub mod window;
mod backend;
mod engine;
mod layer;
mod primitive;
mod settings;
mod text;
@ -19,12 +20,388 @@ pub mod geometry;
pub use iced_graphics as graphics;
pub use iced_graphics::core;
pub use backend::Backend;
pub use layer::Layer;
pub use primitive::Primitive;
pub use settings::Settings;
#[cfg(feature = "geometry")]
pub use geometry::Geometry;
use crate::core::renderer;
use crate::core::{
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
};
use crate::engine::Engine;
use crate::graphics::compositor;
use crate::graphics::text::{Editor, Paragraph};
use crate::graphics::Viewport;
/// A [`tiny-skia`] graphics renderer for [`iced`].
///
/// [`tiny-skia`]: https://github.com/RazrFalcon/tiny-skia
/// [`iced`]: https://github.com/iced-rs/iced
pub type Renderer = iced_graphics::Renderer<Backend>;
#[derive(Debug)]
pub struct Renderer {
default_font: Font,
default_text_size: Pixels,
layers: layer::Stack,
engine: Engine, // TODO: Shared engine
}
impl Renderer {
pub fn new(default_font: Font, default_text_size: Pixels) -> Self {
Self {
default_font,
default_text_size,
layers: layer::Stack::new(),
engine: Engine::new(),
}
}
pub fn layers(&mut self) -> impl Iterator<Item = &Layer> {
self.layers.flush();
self.layers.iter()
}
pub fn draw<T: AsRef<str>>(
&mut self,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: &mut tiny_skia::Mask,
viewport: &Viewport,
damage: &[Rectangle],
background_color: Color,
overlay: &[T],
) {
let physical_size = viewport.physical_size();
let scale_factor = viewport.scale_factor() as f32;
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(engine::into_color(
Color {
a: 0.1,
..background_color
},
)),
anti_alias: false,
..Default::default()
},
tiny_skia::FillRule::default(),
tiny_skia::Transform::identity(),
None,
);
}
self.layers.flush();
for &region in damage {
let region = region * scale_factor;
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(engine::into_color(
background_color,
)),
anti_alias: false,
blend_mode: tiny_skia::BlendMode::Source,
..Default::default()
},
tiny_skia::FillRule::default(),
tiny_skia::Transform::identity(),
None,
);
for layer in self.layers.iter() {
let Some(clip_bounds) = region.intersection(&layer.bounds)
else {
continue;
};
let clip_bounds = clip_bounds * scale_factor;
engine::adjust_clip_mask(clip_mask, clip_bounds);
for (quad, background) in &layer.quads {
self.engine.draw_quad(
quad,
background,
Transformation::scale(scale_factor),
pixels,
clip_mask,
clip_bounds,
);
}
for group in &layer.text {
for text in group.as_slice() {
self.engine.draw_text(
text,
group.transformation()
* Transformation::scale(scale_factor),
pixels,
clip_mask,
clip_bounds,
);
}
}
for group in &layer.primitives {
let Some(new_clip_bounds) =
group.clip_bounds().intersection(&layer.bounds)
else {
continue;
};
engine::adjust_clip_mask(
clip_mask,
new_clip_bounds * scale_factor,
);
for primitive in group.as_slice() {
self.engine.draw_primitive(
primitive,
group.transformation()
* Transformation::scale(scale_factor),
pixels,
clip_mask,
clip_bounds,
);
}
engine::adjust_clip_mask(clip_mask, clip_bounds);
}
for image in &layer.images {
self.engine.draw_image(
image,
Transformation::scale(scale_factor),
pixels,
clip_mask,
clip_bounds,
);
}
}
if !overlay.is_empty() {
pixels.stroke_path(
&path,
&tiny_skia::Paint {
shader: tiny_skia::Shader::SolidColor(
engine::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.engine.trim();
}
}
impl core::Renderer for Renderer {
fn start_layer(&mut self, bounds: Rectangle) {
self.layers.push_clip(bounds);
}
fn end_layer(&mut self) {
self.layers.pop_clip();
}
fn start_transformation(&mut self, transformation: Transformation) {
self.layers.push_transformation(transformation);
}
fn end_transformation(&mut self) {
self.layers.pop_transformation();
}
fn fill_quad(
&mut self,
quad: renderer::Quad,
background: impl Into<Background>,
) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_quad(quad, background.into(), transformation);
}
fn clear(&mut self) {
self.layers.clear();
}
}
impl core::text::Renderer for Renderer {
type Font = Font;
type Paragraph = Paragraph;
type Editor = Editor;
const ICON_FONT: Font = Font::with_name("Iced-Icons");
const CHECKMARK_ICON: char = '\u{f00c}';
const ARROW_DOWN_ICON: char = '\u{e800}';
fn default_font(&self) -> Self::Font {
self.default_font
}
fn default_size(&self) -> Pixels {
self.default_text_size
}
fn fill_paragraph(
&mut self,
text: &Self::Paragraph,
position: Point,
color: Color,
clip_bounds: Rectangle,
) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_paragraph(
text,
position,
color,
clip_bounds,
transformation,
);
}
fn fill_editor(
&mut self,
editor: &Self::Editor,
position: Point,
color: Color,
clip_bounds: Rectangle,
) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_editor(editor, position, color, clip_bounds, transformation);
}
fn fill_text(
&mut self,
text: core::Text,
position: Point,
color: Color,
clip_bounds: Rectangle,
) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_text(text, position, color, clip_bounds, transformation);
}
}
#[cfg(feature = "geometry")]
impl graphics::geometry::Renderer for Renderer {
type Geometry = Geometry;
type Frame = geometry::Frame;
fn new_frame(&self, size: core::Size) -> Self::Frame {
geometry::Frame::new(size)
}
fn draw_geometry(&mut self, geometry: Self::Geometry) {
let (layer, transformation) = self.layers.current_mut();
match geometry {
Geometry::Live {
primitives,
text,
clip_bounds,
} => {
layer.draw_primitive_group(
primitives,
clip_bounds,
transformation,
);
layer.draw_text_group(text, clip_bounds, transformation);
}
Geometry::Cache(cache) => {
layer.draw_primitive_cache(
cache.primitives,
cache.clip_bounds,
transformation,
);
layer.draw_text_cache(
cache.text,
cache.clip_bounds,
transformation,
);
}
}
}
}
impl graphics::mesh::Renderer for Renderer {
fn draw_mesh(&mut self, _mesh: graphics::Mesh) {
log::warn!("iced_tiny_skia does not support drawing meshes");
}
}
#[cfg(feature = "image")]
impl core::image::Renderer for Renderer {
type Handle = core::image::Handle;
fn measure_image(&self, handle: &Self::Handle) -> Size<u32> {
self.engine.raster_pipeline.dimensions(handle)
}
fn draw_image(
&mut self,
handle: Self::Handle,
filter_method: core::image::FilterMethod,
bounds: Rectangle,
) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_image(handle, filter_method, bounds, transformation);
}
}
#[cfg(feature = "svg")]
impl core::svg::Renderer for Renderer {
fn measure_svg(&self, handle: &core::svg::Handle) -> Size<u32> {
self.engine.vector_pipeline.viewport_dimensions(handle)
}
fn draw_svg(
&mut self,
handle: core::svg::Handle,
color: Option<Color>,
bounds: Rectangle,
) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_svg(handle, color, bounds, transformation);
}
}
impl compositor::Default for Renderer {
type Compositor = window::Compositor;
}

View file

@ -1,10 +1,5 @@
use crate::core::Rectangle;
use crate::graphics::{Damage, Mesh};
pub type Primitive = crate::graphics::Primitive<Custom>;
#[derive(Debug, Clone, PartialEq)]
pub enum Custom {
pub enum Primitive {
/// A path filled with some paint.
Fill {
/// The path to fill.
@ -24,29 +19,3 @@ pub enum Custom {
stroke: tiny_skia::Stroke,
},
}
impl Damage for Custom {
fn bounds(&self) -> Rectangle {
match self {
Self::Fill { path, .. } | Self::Stroke { path, .. } => {
let bounds = path.bounds();
Rectangle {
x: bounds.x(),
y: bounds.y(),
width: bounds.width(),
height: bounds.height(),
}
.expand(1.0)
}
}
}
}
impl TryFrom<Mesh> for Custom {
type Error = &'static str;
fn try_from(_mesh: Mesh) -> Result<Self, Self::Error> {
Err("unsupported")
}
}

View file

@ -1,9 +1,9 @@
use crate::core::{Font, Pixels};
use crate::graphics;
/// The settings of a [`Backend`].
/// The settings of a [`Compositor`].
///
/// [`Backend`]: crate::Backend
/// [`Compositor`]: crate::window::Compositor
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Settings {
/// The default [`Font`] to use.

View file

@ -1,5 +1,5 @@
use crate::core::alignment;
use crate::core::text::{LineHeight, Shaping};
use crate::core::text::Shaping;
use crate::core::{
Color, Font, Pixels, Point, Rectangle, Size, Transformation,
};
@ -27,6 +27,8 @@ impl Pipeline {
}
}
// TODO: Shared engine
#[allow(dead_code)]
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
font_system()
.write()
@ -41,7 +43,6 @@ impl Pipeline {
paragraph: &paragraph::Weak,
position: Point,
color: Color,
scale_factor: f32,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: Option<&tiny_skia::Mask>,
transformation: Transformation,
@ -62,7 +63,6 @@ impl Pipeline {
color,
paragraph.horizontal_alignment(),
paragraph.vertical_alignment(),
scale_factor,
pixels,
clip_mask,
transformation,
@ -74,7 +74,6 @@ impl Pipeline {
editor: &editor::Weak,
position: Point,
color: Color,
scale_factor: f32,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: Option<&tiny_skia::Mask>,
transformation: Transformation,
@ -95,7 +94,6 @@ impl Pipeline {
color,
alignment::Horizontal::Left,
alignment::Vertical::Top,
scale_factor,
pixels,
clip_mask,
transformation,
@ -108,17 +106,16 @@ impl Pipeline {
bounds: Rectangle,
color: Color,
size: Pixels,
line_height: LineHeight,
line_height: Pixels,
font: Font,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
shaping: Shaping,
scale_factor: f32,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: Option<&tiny_skia::Mask>,
transformation: Transformation,
) {
let line_height = f32::from(line_height.to_absolute(size));
let line_height = f32::from(line_height);
let mut font_system = font_system().write().expect("Write font system");
let font_system = font_system.raw();
@ -149,7 +146,6 @@ impl Pipeline {
color,
horizontal_alignment,
vertical_alignment,
scale_factor,
pixels,
clip_mask,
transformation,
@ -161,7 +157,6 @@ impl Pipeline {
buffer: &cosmic_text::Buffer,
position: Point,
color: Color,
scale_factor: f32,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: Option<&tiny_skia::Mask>,
transformation: Transformation,
@ -178,7 +173,6 @@ impl Pipeline {
color,
alignment::Horizontal::Left,
alignment::Vertical::Top,
scale_factor,
pixels,
clip_mask,
transformation,
@ -199,12 +193,11 @@ fn draw(
color: Color,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
scale_factor: f32,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: Option<&tiny_skia::Mask>,
transformation: Transformation,
) {
let bounds = bounds * transformation * scale_factor;
let bounds = bounds * transformation;
let x = match horizontal_alignment {
alignment::Horizontal::Left => bounds.x,
@ -222,8 +215,8 @@ fn draw(
for run in buffer.layout_runs() {
for glyph in run.glyphs {
let physical_glyph = glyph
.physical((x, y), scale_factor * transformation.scale_factor());
let physical_glyph =
glyph.physical((x, y), transformation.scale_factor());
if let Some((buffer, placement)) = glyph_cache.allocate(
physical_glyph.cache_key,
@ -247,10 +240,8 @@ fn draw(
pixels.draw_pixmap(
physical_glyph.x + placement.left,
physical_glyph.y - placement.top
+ (run.line_y
* scale_factor
* transformation.scale_factor())
.round() as i32,
+ (run.line_y * transformation.scale_factor()).round()
as i32,
pixmap,
&tiny_skia::PixmapPaint {
opacity,

View file

@ -1,9 +1,8 @@
use crate::core::{Color, Rectangle, Size};
use crate::graphics::compositor::{self, Information};
use crate::graphics::damage;
use crate::graphics::error::{self, Error};
use crate::graphics::{self, Viewport};
use crate::{Backend, Primitive, Renderer, Settings};
use crate::{Layer, Renderer, Settings};
use std::collections::VecDeque;
use std::num::NonZeroU32;
@ -21,7 +20,7 @@ pub struct Surface {
Box<dyn compositor::Window>,
>,
clip_mask: tiny_skia::Mask,
primitive_stack: VecDeque<Vec<Primitive>>,
layer_stack: VecDeque<Vec<Layer>>,
background_color: Color,
max_age: u8,
}
@ -50,7 +49,6 @@ impl crate::graphics::Compositor for Compositor {
fn create_renderer(&self) -> Self::Renderer {
Renderer::new(
Backend::new(),
self.settings.default_font,
self.settings.default_text_size,
)
@ -72,7 +70,7 @@ impl crate::graphics::Compositor for Compositor {
window,
clip_mask: tiny_skia::Mask::new(width, height)
.expect("Create clip mask"),
primitive_stack: VecDeque::new(),
layer_stack: VecDeque::new(),
background_color: Color::BLACK,
max_age: 0,
};
@ -98,7 +96,7 @@ impl crate::graphics::Compositor for Compositor {
surface.clip_mask =
tiny_skia::Mask::new(width, height).expect("Create clip mask");
surface.primitive_stack.clear();
surface.layer_stack.clear();
}
fn fetch_information(&self) -> Information {
@ -116,16 +114,7 @@ impl crate::graphics::Compositor for Compositor {
background_color: Color,
overlay: &[T],
) -> Result<(), compositor::SurfaceError> {
renderer.with_primitives(|backend, primitives| {
present(
backend,
surface,
primitives,
viewport,
background_color,
overlay,
)
})
present(renderer, surface, viewport, background_color, overlay)
}
fn screenshot<T: AsRef<str>>(
@ -136,16 +125,7 @@ impl crate::graphics::Compositor for Compositor {
background_color: Color,
overlay: &[T],
) -> Vec<u8> {
renderer.with_primitives(|backend, primitives| {
screenshot(
surface,
backend,
primitives,
viewport,
background_color,
overlay,
)
})
screenshot(renderer, surface, viewport, background_color, overlay)
}
}
@ -161,49 +141,52 @@ pub fn new<W: compositor::Window>(
}
pub fn present<T: AsRef<str>>(
backend: &mut Backend,
renderer: &mut Renderer,
surface: &mut Surface,
primitives: &[Primitive],
viewport: &Viewport,
background_color: Color,
overlay: &[T],
) -> Result<(), compositor::SurfaceError> {
let physical_size = viewport.physical_size();
let scale_factor = viewport.scale_factor() as f32;
let mut buffer = surface
.window
.buffer_mut()
.map_err(|_| compositor::SurfaceError::Lost)?;
let last_primitives = {
let _last_layers = {
let age = buffer.age();
surface.max_age = surface.max_age.max(age);
surface.primitive_stack.truncate(surface.max_age as usize);
surface.layer_stack.truncate(surface.max_age as usize);
if age > 0 {
surface.primitive_stack.get(age as usize - 1)
surface.layer_stack.get(age as usize - 1)
} else {
None
}
};
let damage = last_primitives
.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())]);
// TODO
// let damage = last_layers
// .and_then(|last_layers| {
// (surface.background_color == background_color)
// .then(|| damage::layers(last_layers, renderer.layers()))
// })
// .unwrap_or_else(|| vec![Rectangle::with_size(viewport.logical_size())]);
let damage = vec![Rectangle::with_size(viewport.logical_size())];
if damage.is_empty() {
return Ok(());
}
surface.primitive_stack.push_front(primitives.to_vec());
surface
.layer_stack
.push_front(renderer.layers().cloned().collect());
surface.background_color = background_color;
let damage = damage::group(damage, scale_factor, physical_size);
// let damage = damage::group(damage, viewport.logical_size());
let mut pixels = tiny_skia::PixmapMut::from_bytes(
bytemuck::cast_slice_mut(&mut buffer),
@ -212,10 +195,9 @@ pub fn present<T: AsRef<str>>(
)
.expect("Create pixel map");
backend.draw(
renderer.draw(
&mut pixels,
&mut surface.clip_mask,
primitives,
viewport,
&damage,
background_color,
@ -226,9 +208,8 @@ pub fn present<T: AsRef<str>>(
}
pub fn screenshot<T: AsRef<str>>(
renderer: &mut Renderer,
surface: &mut Surface,
backend: &mut Backend,
primitives: &[Primitive],
viewport: &Viewport,
background_color: Color,
overlay: &[T],
@ -238,7 +219,7 @@ pub fn screenshot<T: AsRef<str>>(
let mut offscreen_buffer: Vec<u32> =
vec![0; size.width as usize * size.height as usize];
backend.draw(
renderer.draw(
&mut tiny_skia::PixmapMut::from_bytes(
bytemuck::cast_slice_mut(&mut offscreen_buffer),
size.width,
@ -246,7 +227,6 @@ pub fn screenshot<T: AsRef<str>>(
)
.expect("Create offscreen pixel map"),
&mut surface.clip_mask,
primitives,
viewport,
&[Rectangle::with_size(Size::new(
size.width as f32,