diff options
Diffstat (limited to 'tiny_skia/src/backend.rs')
-rw-r--r-- | tiny_skia/src/backend.rs | 1033 |
1 files changed, 0 insertions, 1033 deletions
diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs deleted file mode 100644 index d0f28876..00000000 --- a/tiny_skia/src/backend.rs +++ /dev/null @@ -1,1033 +0,0 @@ -use crate::core::{ - Background, Color, Gradient, Rectangle, Size, Transformation, Vector, -}; -use crate::graphics::backend; -use crate::graphics::text; -use crate::graphics::{Damage, Viewport}; -use crate::primitive::{self, Primitive}; -use crate::window; - -use std::borrow::Cow; - -#[derive(Debug)] -pub struct Backend { - text_pipeline: crate::text::Pipeline, - - #[cfg(feature = "image")] - raster_pipeline: crate::raster::Pipeline, - - #[cfg(feature = "svg")] - vector_pipeline: crate::vector::Pipeline, -} - -impl Backend { - pub fn new() -> Self { - Self { - text_pipeline: crate::text::Pipeline::new(), - - #[cfg(feature = "image")] - raster_pipeline: crate::raster::Pipeline::new(), - - #[cfg(feature = "svg")] - vector_pipeline: crate::vector::Pipeline::new(), - } - } - - pub fn draw<T: AsRef<str>>( - &mut self, - pixels: &mut tiny_skia::PixmapMut<'_>, - clip_mask: &mut tiny_skia::Mask, - primitives: &[Primitive], - 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(into_color(Color { - a: 0.1, - ..background_color - })), - anti_alias: false, - ..Default::default() - }, - tiny_skia::FillRule::default(), - tiny_skia::Transform::identity(), - None, - ); - } - - 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, - blend_mode: tiny_skia::BlendMode::Source, - ..Default::default() - }, - tiny_skia::FillRule::default(), - tiny_skia::Transform::identity(), - None, - ); - - adjust_clip_mask(clip_mask, region); - - for primitive in primitives { - self.draw_primitive( - primitive, - pixels, - clip_mask, - region, - scale_factor, - Transformation::IDENTITY, - ); - } - - 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(); - - #[cfg(feature = "image")] - self.raster_pipeline.trim_cache(); - - #[cfg(feature = "svg")] - self.vector_pipeline.trim_cache(); - } - - fn draw_primitive( - &mut self, - primitive: &Primitive, - pixels: &mut tiny_skia::PixmapMut<'_>, - clip_mask: &mut tiny_skia::Mask, - clip_bounds: Rectangle, - scale_factor: f32, - transformation: Transformation, - ) { - match primitive { - Primitive::Quad { - bounds, - background, - border, - shadow, - } => { - debug_assert!( - bounds.width.is_normal(), - "Quad with non-normal width!" - ); - debug_assert!( - bounds.height.is_normal(), - "Quad with non-normal height!" - ); - - let physical_bounds = (*bounds * transformation) * 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 = into_transform(transformation) - .post_scale(scale_factor, scale_factor); - - // Make sure the border radius is not larger than the bounds - let border_width = border - .width - .min(bounds.width / 2.0) - .min(bounds.height / 2.0); - - let mut fill_border_radius = <[f32; 4]>::from(border.radius); - for radius in &mut fill_border_radius { - *radius = (*radius) - .min(bounds.width / 2.0) - .min(bounds.height / 2.0); - } - let path = rounded_rectangle(*bounds, fill_border_radius); - - if shadow.color.a > 0.0 { - let shadow_bounds = (Rectangle { - x: bounds.x + shadow.offset.x - shadow.blur_radius, - y: bounds.y + shadow.offset.y - shadow.blur_radius, - width: bounds.width + shadow.blur_radius * 2.0, - height: bounds.height + shadow.blur_radius * 2.0, - } * transformation) - * scale_factor; - - let radii = fill_border_radius - .into_iter() - .map(|radius| radius * 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 - * scale_factor) - - half_width, - y - physical_bounds.position().y - - (shadow.offset.y - * scale_factor) - - half_height, - ), - size, - &radii, - ) - .max(0.0); - let shadow_alpha = 1.0 - - smoothstep( - -shadow.blur_radius * scale_factor, - shadow.blur_radius * 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(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: bounds.x + border_width / 2.0, - y: bounds.y + border_width / 2.0, - width: bounds.width - border_width, - height: bounds.height - border_width, - }; - - // Make sure the border radius is correct - let mut border_radius = <[f32; 4]>::from(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(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( - bounds.width as u32, - bounds.height as u32, - ) - .unwrap(); - - let mut quad_mask = tiny_skia::Mask::new( - bounds.width as u32, - bounds.height as u32, - ) - .unwrap(); - - let zero_bounds = Rectangle { - x: 0.0, - y: 0.0, - width: bounds.width, - height: 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: bounds.width - border_width, - height: 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(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( - bounds.x as i32, - bounds.y as i32, - temp_pixmap.as_ref(), - &tiny_skia::PixmapPaint::default(), - transform, - clip_mask, - ); - } - } - } - Primitive::Paragraph { - paragraph, - position, - color, - clip_bounds: _, // TODO: Support text clip bounds - } => { - let physical_bounds = - Rectangle::new(*position, paragraph.min_bounds) - * transformation - * 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_paragraph( - paragraph, - *position, - *color, - scale_factor, - pixels, - clip_mask, - transformation, - ); - } - Primitive::Editor { - editor, - position, - color, - clip_bounds: _, // TODO: Support text clip bounds - } => { - let physical_bounds = Rectangle::new(*position, editor.bounds) - * transformation - * 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_editor( - editor, - *position, - *color, - scale_factor, - pixels, - clip_mask, - transformation, - ); - } - Primitive::Text { - content, - bounds, - color, - size, - line_height, - font, - horizontal_alignment, - vertical_alignment, - shaping, - clip_bounds: _, // TODO: Support text clip bounds - } => { - let physical_bounds = - primitive.bounds() * transformation * 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_cached( - content, - *bounds, - *color, - *size, - *line_height, - *font, - *horizontal_alignment, - *vertical_alignment, - *shaping, - scale_factor, - pixels, - clip_mask, - transformation, - ); - } - Primitive::RawText(text::Raw { - buffer, - position, - color, - clip_bounds: _, // TODO: Support text clip bounds - }) => { - let Some(buffer) = buffer.upgrade() else { - return; - }; - - let (width, height) = buffer.size(); - - let physical_bounds = - Rectangle::new(*position, Size::new(width, height)) - * transformation - * 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_raw( - &buffer, - *position, - *color, - scale_factor, - pixels, - clip_mask, - transformation, - ); - } - #[cfg(feature = "image")] - Primitive::Image { - handle, - filter_method, - bounds, - } => { - let physical_bounds = (*bounds * transformation) * 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 = into_transform(transformation) - .post_scale(scale_factor, scale_factor); - - self.raster_pipeline.draw( - handle, - *filter_method, - *bounds, - pixels, - transform, - clip_mask, - ); - } - #[cfg(not(feature = "image"))] - Primitive::Image { .. } => { - log::warn!( - "Unsupported primitive in `iced_tiny_skia`: {primitive:?}", - ); - } - #[cfg(feature = "svg")] - Primitive::Svg { - handle, - bounds, - color, - } => { - let physical_bounds = (*bounds * transformation) * 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 * transformation) * scale_factor, - pixels, - clip_mask, - ); - } - #[cfg(not(feature = "svg"))] - Primitive::Svg { .. } => { - log::warn!( - "Unsupported primitive in `iced_tiny_skia`: {primitive:?}", - ); - } - Primitive::Custom(primitive::Custom::Fill { - path, - paint, - rule, - }) => { - let bounds = path.bounds(); - - let physical_bounds = (Rectangle { - x: bounds.x(), - y: bounds.y(), - width: bounds.width(), - height: bounds.height(), - } * transformation) - * 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, - *rule, - into_transform(transformation) - .post_scale(scale_factor, scale_factor), - clip_mask, - ); - } - Primitive::Custom(primitive::Custom::Stroke { - path, - paint, - stroke, - }) => { - let bounds = path.bounds(); - - let physical_bounds = (Rectangle { - x: bounds.x(), - y: bounds.y(), - width: bounds.width().max(1.0), - height: bounds.height().max(1.0), - } * transformation) - * 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, - stroke, - into_transform(transformation) - .post_scale(scale_factor, scale_factor), - clip_mask, - ); - } - Primitive::Group { primitives } => { - for primitive in primitives { - self.draw_primitive( - primitive, - pixels, - clip_mask, - clip_bounds, - scale_factor, - transformation, - ); - } - } - Primitive::Transform { - transformation: new_transformation, - content, - } => { - self.draw_primitive( - content, - pixels, - clip_mask, - clip_bounds, - scale_factor, - transformation * *new_transformation, - ); - } - Primitive::Clip { bounds, content } => { - let bounds = (*bounds * transformation) * scale_factor; - - if bounds == clip_bounds { - self.draw_primitive( - content, - pixels, - clip_mask, - bounds, - scale_factor, - transformation, - ); - } 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, - bounds, - scale_factor, - transformation, - ); - - adjust_clip_mask(clip_mask, clip_bounds); - } - } - Primitive::Cache { content } => { - self.draw_primitive( - content, - pixels, - clip_mask, - clip_bounds, - scale_factor, - transformation, - ); - } - } - } -} - -impl Default for Backend { - fn default() -> Self { - Self::new() - } -} - -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 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(), - ); -} - -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 -} - -impl backend::Backend for Backend { - type Primitive = primitive::Custom; - type Compositor = window::Compositor; -} - -impl backend::Text for Backend { - fn load_font(&mut self, font: Cow<'static, [u8]>) { - self.text_pipeline.load_font(font); - } -} - -#[cfg(feature = "image")] -impl backend::Image for Backend { - fn dimensions( - &self, - handle: &crate::core::image::Handle, - ) -> crate::core::Size<u32> { - self.raster_pipeline.dimensions(handle) - } -} - -#[cfg(feature = "svg")] -impl backend::Svg for Backend { - fn viewport_dimensions( - &self, - handle: &crate::core::svg::Handle, - ) -> crate::core::Size<u32> { - self.vector_pipeline.viewport_dimensions(handle) - } -} - -#[cfg(feature = "geometry")] -impl crate::graphics::geometry::Backend for Backend { - type Frame = crate::geometry::Frame; - - fn new_frame(&self, size: Size) -> Self::Frame { - crate::geometry::Frame::new(size) - } -} |