diff --git a/gpu/src/gpu_backend.rs b/gpu/src/gpu_backend.rs index d679e9b02..3d40105fd 100644 --- a/gpu/src/gpu_backend.rs +++ b/gpu/src/gpu_backend.rs @@ -241,12 +241,7 @@ where if let Some((rect, mask_head)) = self.new_mask_layer(path) { self.update_to_radial_gradient_indices(); let prim: RadialGradientPrimitive = RadialGradientPrimitive { - transform: radial_gradient - .transform - .then(&ts) - .inverse() - .unwrap() - .to_array(), + transform: ts.inverse().unwrap().to_array(), stop_start: self.radial_gradient_stops.len() as u32, stop_cnt: radial_gradient.stops.len() as u32, start_center: radial_gradient.start_center.to_array(), @@ -279,12 +274,7 @@ where if let Some((rect, mask_head)) = self.new_mask_layer(path) { self.update_to_linear_gradient_indices(); let prim: LinearGradientPrimitive = LinearGradientPrimitive { - transform: linear_gradient - .transform - .then(&ts) - .inverse() - .unwrap() - .to_array(), + transform: ts.inverse().unwrap().to_array(), stop_start: self.linear_gradient_stops.len() as u32, stop_cnt: linear_gradient.stops.len() as u32, start_position: linear_gradient.start.to_array(), @@ -541,10 +531,7 @@ mod tests { use ribir_algo::ShareResource; use ribir_dev_helper::*; use ribir_geom::*; - use ribir_painter::{ - color::{LinearGradient, RadialGradient}, - Brush, Color, GradientStop, Painter, Path, PixelImage, - }; + use ribir_painter::{Brush, Color, Painter, Path, PixelImage, Svg}; fn painter(bounds: Size) -> Painter { Painter::new(Rect::from_size(bounds)) } @@ -682,48 +669,13 @@ mod tests { painter } - painter_backend_eq_image_test!(draw_radial_gradient); - fn draw_radial_gradient() -> Painter { + painter_backend_eq_image_test!(draw_svg_gradient); + fn draw_svg_gradient() -> Painter { let mut painter = painter(Size::new(64., 64.)); - let brush = Brush::RadialGradient(RadialGradient { - start_center: Point::new(16., 32.), - start_radius: 0., - end_center: Point::new(32., 32.), - end_radius: 32., - stops: vec![ - GradientStop::new(Color::RED, 0.), - GradientStop::new(Color::BLUE, 1.), - ], - transform: Transform::translation(16., 0.), - ..Default::default() - }); - - painter - .set_brush(brush) - .rect(&Rect::from_size(Size::new(64., 64.))) - .fill(); - - painter - } - - painter_backend_eq_image_test!(draw_linear_gradient); - fn draw_linear_gradient() -> Painter { - let mut painter = painter(Size::new(32., 32.)); - let brush = Brush::LinearGradient(LinearGradient { - start: Point::new(0., 0.), - end: Point::new(16., 16.), - stops: vec![ - GradientStop::new(Color::RED, 0.), - GradientStop::new(Color::BLUE, 1.), - ], - ..Default::default() - }); - - painter - .set_brush(brush) - .rect(&Rect::from_size(Size::new(64., 64.))) - .fill(); + let svg = + Svg::parse_from_bytes(include_bytes!("../../tests/assets/fill_with_gradient.svg")).unwrap(); + painter.draw_svg(&svg); painter } } diff --git a/gpu/src/gpu_backend/textures_mgr.rs b/gpu/src/gpu_backend/textures_mgr.rs index d4ee365d0..be1fb41d5 100644 --- a/gpu/src/gpu_backend/textures_mgr.rs +++ b/gpu/src/gpu_backend/textures_mgr.rs @@ -108,8 +108,8 @@ where // apply path transform matrix to view. .then(path_ts) } - - let prefer_scale: f32 = transform.m11.abs().max(transform.m22.abs()); + let Transform { m11, m12, m21, m22, .. } = *transform; + let prefer_scale: f32 = (m11.abs() + m12.abs()).max(m21.abs() + m22.abs()); let key = PathKey::from_path(path); if let Some(h) = self diff --git a/painter/src/color.rs b/painter/src/color.rs index 3144f9d2a..19a0fa7c2 100644 --- a/painter/src/color.rs +++ b/painter/src/color.rs @@ -1,5 +1,5 @@ use material_color_utilities_rs::htc; -use ribir_geom::{Point, Transform}; +use ribir_geom::Point; use serde::{Deserialize, Serialize}; use crate::SpreadMethod; @@ -30,7 +30,6 @@ pub struct RadialGradient { pub end_center: Point, pub end_radius: f32, pub stops: Vec, - pub transform: Transform, pub spread_method: SpreadMethod, } @@ -39,7 +38,6 @@ pub struct LinearGradient { pub start: Point, pub end: Point, pub stops: Vec, - pub transform: Transform, pub spread_method: SpreadMethod, } diff --git a/painter/src/painter.rs b/painter/src/painter.rs index 569eec15a..8182a608a 100644 --- a/painter/src/painter.rs +++ b/painter/src/painter.rs @@ -120,6 +120,36 @@ pub enum PaintCommand { PopClip, } +impl PaintCommand { + pub fn pre_transform(mut self, transform: &Transform) -> Self { + match &mut self { + PaintCommand::ColorPath { path, .. } + | PaintCommand::ImgPath { path, .. } + | PaintCommand::RadialGradient { path, .. } + | PaintCommand::LinearGradient { path, .. } + | PaintCommand::Clip(path) => { + path.transform = path.transform.then(transform); + } + PaintCommand::PopClip => {} + } + self + } + + pub fn transform(mut self, transform: &Transform) -> Self { + match &mut self { + PaintCommand::ColorPath { path, .. } + | PaintCommand::ImgPath { path, .. } + | PaintCommand::RadialGradient { path, .. } + | PaintCommand::LinearGradient { path, .. } + | PaintCommand::Clip(path) => { + path.transform = transform.then(&path.transform); + } + PaintCommand::PopClip => {} + } + self + } +} + #[derive(Clone)] struct PainterState { /// The line width use to stroke path. @@ -474,16 +504,11 @@ impl Painter { } pub fn draw_svg(&mut self, svg: &Svg) -> &mut Self { - self.scale(svg.view_scale.x, svg.view_scale.y); - svg.paths.iter().for_each(|c| { - self.set_brush(c.brush.clone()); - match &c.style { - PathPaintStyle::Fill => self.fill_path(c.path.clone()), - PathPaintStyle::Stroke(options) => self - .set_strokes(options.clone()) - .stroke_path(c.path.clone()), - }; - }); + let transform = *self.get_transform(); + svg + .paint_commands + .iter() + .for_each(|c| self.commands.push(c.clone().pre_transform(&transform))); self } diff --git a/painter/src/svg.rs b/painter/src/svg.rs index b50eb78bf..b7fa56ae7 100644 --- a/painter/src/svg.rs +++ b/painter/src/svg.rs @@ -1,8 +1,8 @@ use crate::{ color::{LinearGradient, RadialGradient}, - Brush, Color, GradientStop, LineCap, LineJoin, Path, PathPaintStyle, StrokeOptions, + Brush, Color, GradientStop, LineCap, LineJoin, PaintCommand, Path, StrokeOptions, }; -use ribir_geom::{Point, Size, Transform, Vector}; +use ribir_geom::{Point, Rect, Size, Transform}; use serde::{Deserialize, Serialize}; use std::{error::Error, io::Read}; use usvg::{Options, Stop, Tree, TreeParsing}; @@ -10,15 +10,7 @@ use usvg::{Options, Stop, Tree, TreeParsing}; #[derive(Serialize, Deserialize, Clone)] pub struct Svg { pub size: Size, - pub view_scale: Vector, - pub paths: Box<[SvgPath]>, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct SvgPath { - pub path: Path, - pub brush: Brush, - pub style: PathPaintStyle, + pub paint_commands: Vec, } /// Fits size into a viewbox. copy from resvg @@ -43,33 +35,29 @@ impl Svg { let size = tree.size; let fit_size = fit_view_box(size, &tree.view_box); - let view_scale = Vector::new( - size.width() / fit_size.width(), - size.height() / fit_size.height(), - ) - .to_f32(); - let t = Transform::translation(-view_rect.x(), -view_rect.y()); - - let mut t_stack = TransformStack::new(t); - let mut paths = vec![]; - + let bound_rect = Rect::from_size(Size::new(size.width(), size.height())); + let mut painter = crate::Painter::new(bound_rect); + painter.clip(Path::rect(&bound_rect)); + painter.apply_transform( + &Transform::translation(-view_rect.x(), -view_rect.y()).then_scale( + size.width() / fit_size.width(), + size.height() / fit_size.height(), + ), + ); tree.root.traverse().for_each(|edge| match edge { rctree::NodeEdge::Start(node) => { use usvg::NodeKind; - + painter.save(); match &*node.borrow() { NodeKind::Path(p) => { - t_stack.push(matrix_convert(p.transform)); + painter.apply_transform(&matrix_convert(p.transform)); let path = usvg_path_to_path(p); - let path = path.transform(t_stack.current_transform()); if let Some(ref fill) = p.fill { - let brush = brush_from_usvg_paint(&fill.paint, fill.opacity, &size); - - paths.push(SvgPath { - path: path.clone(), - brush, - style: PathPaintStyle::Fill, - }); + let (brush, transform) = brush_from_usvg_paint(&fill.paint, fill.opacity, &size); + let mut painter = painter.save_guard(); + painter.set_brush(brush.clone()); + painter.apply_transform(&transform); + painter.fill_path(path.clone().transform(&transform.inverse().unwrap())); //&o_ts.then(&n_ts.inverse().unwrap()))); } if let Some(ref stroke) = p.stroke { @@ -90,12 +78,12 @@ impl Svg { miter_limit: stroke.miterlimit.get(), }; - let brush = brush_from_usvg_paint(&stroke.paint, stroke.opacity, &size); - paths.push(SvgPath { - path, - brush, - style: PathPaintStyle::Stroke(options), - }); + let (brush, transform) = brush_from_usvg_paint(&stroke.paint, stroke.opacity, &size); + let mut painter = painter.save_guard(); + painter.set_brush(brush.clone()); + painter.apply_transform(&transform); + painter.set_strokes(options); + painter.stroke_path(path.transform(&transform.inverse().unwrap())); }; } NodeKind::Image(_) => { @@ -103,7 +91,7 @@ impl Svg { log::warn!("[painter]: not support draw embed image in svg, ignored!"); } NodeKind::Group(ref g) => { - t_stack.push(matrix_convert(g.transform)); + painter.apply_transform(&matrix_convert(g.transform)); // todo; if g.opacity.get() != 1. { log::warn!("[painter]: not support `opacity` in svg, ignored!"); @@ -124,14 +112,13 @@ impl Svg { } } rctree::NodeEdge::End(_) => { - t_stack.pop(); + painter.restore(); } }); Ok(Svg { size: Size::new(size.width(), size.height()), - paths: paths.into_boxed_slice(), - view_scale, + paint_commands: painter.finish(), }) } @@ -182,11 +169,18 @@ fn matrix_convert(t: usvg::Transform) -> Transform { Transform::new(sx, ky, kx, sy, tx, ty) } -fn brush_from_usvg_paint(paint: &usvg::Paint, opacity: usvg::Opacity, size: &usvg::Size) -> Brush { +fn brush_from_usvg_paint( + paint: &usvg::Paint, + opacity: usvg::Opacity, + size: &usvg::Size, +) -> (Brush, Transform) { match paint { - usvg::Paint::Color(usvg::Color { red, green, blue }) => Color::from_rgb(*red, *green, *blue) - .with_alpha(opacity.get()) - .into(), + usvg::Paint::Color(usvg::Color { red, green, blue }) => ( + Color::from_rgb(*red, *green, *blue) + .with_alpha(opacity.get()) + .into(), + Transform::identity(), + ), usvg::Paint::LinearGradient(linear) => { let stops = convert_to_gradient_stops(&linear.stops); let size_scale = match linear.units { @@ -195,12 +189,15 @@ fn brush_from_usvg_paint(paint: &usvg::Paint, opacity: usvg::Opacity, size: &usv }; let gradient = LinearGradient { start: Point::new(linear.x1 * size_scale.0, linear.y1 * size_scale.1), - end: Point::new(linear.y2 * size_scale.0, linear.y2 * size_scale.1), + end: Point::new(linear.x2 * size_scale.0, linear.y2 * size_scale.1), stops, - transform: matrix_convert(linear.transform), spread_method: linear.spread_method.into(), }; - Brush::LinearGradient(gradient) + + ( + Brush::LinearGradient(gradient), + matrix_convert(linear.transform), + ) } usvg::Paint::RadialGradient(radial_gradient) => { let stops = convert_to_gradient_stops(&radial_gradient.stops); @@ -213,21 +210,24 @@ fn brush_from_usvg_paint(paint: &usvg::Paint, opacity: usvg::Opacity, size: &usv radial_gradient.fx * size_scale.0, radial_gradient.fy * size_scale.1, ), - start_radius: 0., + start_radius: 0., // usvg not support fr end_center: Point::new( radial_gradient.cx * size_scale.0, radial_gradient.cy * size_scale.1, ), end_radius: radial_gradient.r.get() * size_scale.0, stops, - transform: matrix_convert(radial_gradient.transform), spread_method: radial_gradient.spread_method.into(), }; - Brush::RadialGradient(gradient) + + ( + Brush::RadialGradient(gradient), + matrix_convert(radial_gradient.transform), + ) } paint => { log::warn!("[painter]: not support `{paint:?}` in svg, use black instead!"); - Color::BLACK.into() + (Color::BLACK.into(), Transform::identity()) } } } @@ -260,22 +260,3 @@ fn convert_to_gradient_stops(stops: &Vec) -> Vec { } stops } - -struct TransformStack { - stack: Vec, -} - -impl TransformStack { - fn new(t: Transform) -> Self { TransformStack { stack: vec![t] } } - - fn push(&mut self, mut t: Transform) { - if let Some(p) = self.stack.last() { - t = p.then(&t); - } - self.stack.push(t); - } - - fn pop(&mut self) -> Option { self.stack.pop() } - - fn current_transform(&self) -> &Transform { self.stack.last().unwrap() } -} diff --git a/test_cases/ribir_gpu/gpu_backend/tests/draw_linear_gradient_wgpu.png b/test_cases/ribir_gpu/gpu_backend/tests/draw_linear_gradient_wgpu.png deleted file mode 100644 index c1d472eac..000000000 Binary files a/test_cases/ribir_gpu/gpu_backend/tests/draw_linear_gradient_wgpu.png and /dev/null differ diff --git a/test_cases/ribir_gpu/gpu_backend/tests/draw_radial_gradient_wgpu.png b/test_cases/ribir_gpu/gpu_backend/tests/draw_radial_gradient_wgpu.png deleted file mode 100644 index f9922f889..000000000 Binary files a/test_cases/ribir_gpu/gpu_backend/tests/draw_radial_gradient_wgpu.png and /dev/null differ diff --git a/test_cases/ribir_gpu/gpu_backend/tests/draw_svg_gradient_wgpu.png b/test_cases/ribir_gpu/gpu_backend/tests/draw_svg_gradient_wgpu.png new file mode 100644 index 000000000..c040d78ae Binary files /dev/null and b/test_cases/ribir_gpu/gpu_backend/tests/draw_svg_gradient_wgpu.png differ diff --git a/tests/assets/fill_with_gradient.svg b/tests/assets/fill_with_gradient.svg new file mode 100644 index 000000000..7723165eb --- /dev/null +++ b/tests/assets/fill_with_gradient.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/include_svg_test.rs b/tests/include_svg_test.rs index e63a91416..35eb415c8 100644 --- a/tests/include_svg_test.rs +++ b/tests/include_svg_test.rs @@ -3,5 +3,5 @@ use ribir::prelude::{include_svg, Svg}; #[test] fn include_svg() { let svg: Svg = include_svg!("./assets/test1.svg"); - assert_eq!(svg.paths.len(), 2); + assert_eq!(svg.paint_commands.len(), 2); }