From ecac9648c2b2efe35ae711805e19458d8694e0b8 Mon Sep 17 00:00:00 2001 From: "Spencer C. Imbleau" Date: Sun, 20 Aug 2023 14:18:53 -0400 Subject: [PATCH 1/6] Add gradient support Co-authored-by: Sebastian J. Hamel --- integrations/vello_svg/src/lib.rs | 44 ++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/integrations/vello_svg/src/lib.rs b/integrations/vello_svg/src/lib.rs index 894f812d9..4157f8c09 100644 --- a/integrations/vello_svg/src/lib.rs +++ b/integrations/vello_svg/src/lib.rs @@ -21,7 +21,6 @@ //! Missing features include: //! - embedded images //! - text -//! - gradients //! - group opacity //! - mix-blend-modes //! - clipping @@ -175,8 +174,47 @@ fn paint_to_brush(paint: &usvg::Paint, opacity: usvg::Opacity) -> Option color.blue, opacity.to_u8(), ))), - usvg::Paint::LinearGradient(_) => None, - usvg::Paint::RadialGradient(_) => None, + usvg::Paint::LinearGradient(gr) => { + let stops: Vec = gr + .stops + .iter() + .map(|stop| { + vello::peniko::Color::rgba8( + stop.color.red, + stop.color.green, + stop.color.blue, + stop.opacity.to_u8(), + ) + }) + .collect(); + let start: vello::kurbo::Point = gr.transform.apply(gr.x1, gr.y1).into(); + let end: vello::kurbo::Point = gr.transform.apply(gr.x2, gr.y2).into(); + let gradient = + vello::peniko::Gradient::new_linear(start, end).with_stops(stops.as_slice()); + Some(Brush::Gradient(gradient)) + } + usvg::Paint::RadialGradient(gr) => { + let stops: Vec = gr + .stops + .iter() + .map(|stop| { + vello::peniko::Color::rgba8( + stop.color.red, + stop.color.green, + stop.color.blue, + stop.opacity.to_u8(), + ) + }) + .collect(); + let center: vello::kurbo::Point = gr.transform.apply(gr.cx, gr.cy).into(); + // TODO: SVG has a scaleX and scaleY - But peniko::Gradient::new_radial only takes a radius. + let (sx, sy) = gr.transform.get_scale(); + let radius = sx.min(sy) as f32; // compromise: take the smaller scale axis I guess? + let gradient = + vello::peniko::Gradient::new_radial(center, radius).with_stops(stops.as_slice()); + + Some(Brush::Gradient(gradient)) + } usvg::Paint::Pattern(_) => None, } } From f05b3e9171b34e38b8a6cafc63cd51dd5a01b11d Mon Sep 17 00:00:00 2001 From: "Spencer C. Imbleau" Date: Sat, 2 Sep 2023 10:43:31 -0400 Subject: [PATCH 2/6] fixes transform and radii --- integrations/vello_svg/src/lib.rs | 105 +++++++++++++++++++----------- 1 file changed, 67 insertions(+), 38 deletions(-) diff --git a/integrations/vello_svg/src/lib.rs b/integrations/vello_svg/src/lib.rs index 4157f8c09..181a2b04d 100644 --- a/integrations/vello_svg/src/lib.rs +++ b/integrations/vello_svg/src/lib.rs @@ -112,21 +112,28 @@ pub fn render_tree_with Result<(), E // FIXME: let path.paint_order determine the fill/stroke order. if let Some(fill) = &path.fill { - if let Some(brush) = paint_to_brush(&fill.paint, fill.opacity) { + if let Some((brush, transform)) = paint_to_brush(&fill.paint, fill.opacity) { // FIXME: Set the fill rule - sb.fill(Fill::NonZero, transform, &brush, None, &local_path); + sb.fill( + Fill::NonZero, + transform, + &brush, + Some(transform), + &local_path, + ); } else { on_err(sb, &elt)?; } } if let Some(stroke) = &path.stroke { - if let Some(brush) = paint_to_brush(&stroke.paint, stroke.opacity) { + if let Some((brush, transform)) = paint_to_brush(&stroke.paint, stroke.opacity) + { // FIXME: handle stroke options such as linecap, linejoin, etc. sb.stroke( &Stroke::new(stroke.width.get() as f32), transform, &brush, - None, + Some(transform), &local_path, ); } else { @@ -166,54 +173,76 @@ pub fn default_error_handler(sb: &mut SceneBuilder, node: &usvg::Node) -> Result Ok(()) } -fn paint_to_brush(paint: &usvg::Paint, opacity: usvg::Opacity) -> Option { +fn paint_to_brush(paint: &usvg::Paint, opacity: usvg::Opacity) -> Option<(Brush, Affine)> { match paint { - usvg::Paint::Color(color) => Some(Brush::Solid(Color::rgba8( - color.red, - color.green, - color.blue, - opacity.to_u8(), - ))), + usvg::Paint::Color(color) => Some(( + Brush::Solid(Color::rgba8( + color.red, + color.green, + color.blue, + opacity.to_u8(), + )), + Affine::default(), + )), usvg::Paint::LinearGradient(gr) => { - let stops: Vec = gr + let stops: Vec = gr .stops .iter() .map(|stop| { - vello::peniko::Color::rgba8( - stop.color.red, - stop.color.green, - stop.color.blue, - stop.opacity.to_u8(), - ) + let mut cstop = vello::peniko::ColorStop::default(); + cstop.color.r = stop.color.red; + cstop.color.g = stop.color.green; + cstop.color.b = stop.color.blue; + cstop.color.a = stop.opacity.to_u8(); + cstop.offset = stop.offset.get() as f32; + cstop }) .collect(); - let start: vello::kurbo::Point = gr.transform.apply(gr.x1, gr.y1).into(); - let end: vello::kurbo::Point = gr.transform.apply(gr.x2, gr.y2).into(); - let gradient = - vello::peniko::Gradient::new_linear(start, end).with_stops(stops.as_slice()); - Some(Brush::Gradient(gradient)) + let gradient = vello::peniko::Gradient::new_linear((gr.x1, gr.y1), (gr.x2, gr.y2)) + .with_stops(stops.as_slice()); + let transform = Affine::new([ + gr.transform.a, + gr.transform.b, + gr.transform.c, + gr.transform.d, + gr.transform.e, + gr.transform.f, + ]); + Some((Brush::Gradient(gradient), transform)) } usvg::Paint::RadialGradient(gr) => { - let stops: Vec = gr + let stops: Vec = gr .stops .iter() .map(|stop| { - vello::peniko::Color::rgba8( - stop.color.red, - stop.color.green, - stop.color.blue, - stop.opacity.to_u8(), - ) + let mut cstop = vello::peniko::ColorStop::default(); + cstop.color.r = stop.color.red; + cstop.color.g = stop.color.green; + cstop.color.b = stop.color.blue; + cstop.color.a = stop.opacity.to_u8(); + cstop.offset = stop.offset.get() as f32; + cstop }) .collect(); - let center: vello::kurbo::Point = gr.transform.apply(gr.cx, gr.cy).into(); - // TODO: SVG has a scaleX and scaleY - But peniko::Gradient::new_radial only takes a radius. - let (sx, sy) = gr.transform.get_scale(); - let radius = sx.min(sy) as f32; // compromise: take the smaller scale axis I guess? - let gradient = - vello::peniko::Gradient::new_radial(center, radius).with_stops(stops.as_slice()); - - Some(Brush::Gradient(gradient)) + // It doesn't look like usvg exposes the fr attribute (the focal point, or start circle radius) so we just pass the r attribute for both. + let start_radius = gr.r.get() as f32; + let end_radius = gr.r.get() as f32; + let gradient = vello::peniko::Gradient::new_two_point_radial( + (gr.fx, gr.fy), + start_radius, + (gr.cx, gr.cy), + end_radius, + ) + .with_stops(stops.as_slice()); + let transform = Affine::new([ + gr.transform.a, + gr.transform.b, + gr.transform.c, + gr.transform.d, + gr.transform.e, + gr.transform.f, + ]); + Some((Brush::Gradient(gradient), transform)) } usvg::Paint::Pattern(_) => None, } From 425c63c7b190b555d38fcf0633db1544299feea0 Mon Sep 17 00:00:00 2001 From: "Spencer C. Imbleau" Date: Sat, 2 Sep 2023 10:54:30 -0400 Subject: [PATCH 3/6] Affine::default() -> IDENTITY --- integrations/vello_svg/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/vello_svg/src/lib.rs b/integrations/vello_svg/src/lib.rs index 181a2b04d..6fe230426 100644 --- a/integrations/vello_svg/src/lib.rs +++ b/integrations/vello_svg/src/lib.rs @@ -182,7 +182,7 @@ fn paint_to_brush(paint: &usvg::Paint, opacity: usvg::Opacity) -> Option<(Brush, color.blue, opacity.to_u8(), )), - Affine::default(), + Affine::IDENTITY, )), usvg::Paint::LinearGradient(gr) => { let stops: Vec = gr From 6755addda67cd2212086fc7f6c48c45e7de0e1b8 Mon Sep 17 00:00:00 2001 From: "Spencer C. Imbleau" Date: Sat, 2 Sep 2023 11:58:04 -0400 Subject: [PATCH 4/6] fixed name collision and rendering --- integrations/vello_svg/src/lib.rs | 49 ++++++++++++++++++------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/integrations/vello_svg/src/lib.rs b/integrations/vello_svg/src/lib.rs index 6fe230426..a0c22b635 100644 --- a/integrations/vello_svg/src/lib.rs +++ b/integrations/vello_svg/src/lib.rs @@ -112,13 +112,15 @@ pub fn render_tree_with Result<(), E // FIXME: let path.paint_order determine the fill/stroke order. if let Some(fill) = &path.fill { - if let Some((brush, transform)) = paint_to_brush(&fill.paint, fill.opacity) { + if let Some((brush, brush_transform)) = + paint_to_brush(&fill.paint, fill.opacity) + { // FIXME: Set the fill rule sb.fill( Fill::NonZero, transform, &brush, - Some(transform), + brush_transform, &local_path, ); } else { @@ -126,14 +128,15 @@ pub fn render_tree_with Result<(), E } } if let Some(stroke) = &path.stroke { - if let Some((brush, transform)) = paint_to_brush(&stroke.paint, stroke.opacity) + if let Some((brush, brush_transform)) = + paint_to_brush(&stroke.paint, stroke.opacity) { // FIXME: handle stroke options such as linecap, linejoin, etc. sb.stroke( &Stroke::new(stroke.width.get() as f32), transform, &brush, - Some(transform), + brush_transform, &local_path, ); } else { @@ -173,7 +176,7 @@ pub fn default_error_handler(sb: &mut SceneBuilder, node: &usvg::Node) -> Result Ok(()) } -fn paint_to_brush(paint: &usvg::Paint, opacity: usvg::Opacity) -> Option<(Brush, Affine)> { +fn paint_to_brush(paint: &usvg::Paint, opacity: usvg::Opacity) -> Option<(Brush, Option)> { match paint { usvg::Paint::Color(color) => Some(( Brush::Solid(Color::rgba8( @@ -182,7 +185,7 @@ fn paint_to_brush(paint: &usvg::Paint, opacity: usvg::Opacity) -> Option<(Brush, color.blue, opacity.to_u8(), )), - Affine::IDENTITY, + None, )), usvg::Paint::LinearGradient(gr) => { let stops: Vec = gr @@ -194,12 +197,12 @@ fn paint_to_brush(paint: &usvg::Paint, opacity: usvg::Opacity) -> Option<(Brush, cstop.color.g = stop.color.green; cstop.color.b = stop.color.blue; cstop.color.a = stop.opacity.to_u8(); - cstop.offset = stop.offset.get() as f32; + cstop.offset = (stop.offset * opacity).get() as f32; cstop }) .collect(); - let gradient = vello::peniko::Gradient::new_linear((gr.x1, gr.y1), (gr.x2, gr.y2)) - .with_stops(stops.as_slice()); + let start: vello::kurbo::Point = (gr.x1, gr.y1).into(); + let end: vello::kurbo::Point = (gr.x2, gr.y2).into(); let transform = Affine::new([ gr.transform.a, gr.transform.b, @@ -208,7 +211,9 @@ fn paint_to_brush(paint: &usvg::Paint, opacity: usvg::Opacity) -> Option<(Brush, gr.transform.e, gr.transform.f, ]); - Some((Brush::Gradient(gradient), transform)) + let gradient = + vello::peniko::Gradient::new_linear(start, end).with_stops(stops.as_slice()); + Some((Brush::Gradient(gradient), Some(transform))) } usvg::Paint::RadialGradient(gr) => { let stops: Vec = gr @@ -220,20 +225,15 @@ fn paint_to_brush(paint: &usvg::Paint, opacity: usvg::Opacity) -> Option<(Brush, cstop.color.g = stop.color.green; cstop.color.b = stop.color.blue; cstop.color.a = stop.opacity.to_u8(); - cstop.offset = stop.offset.get() as f32; + cstop.offset = (stop.offset * opacity).get() as f32; cstop }) .collect(); - // It doesn't look like usvg exposes the fr attribute (the focal point, or start circle radius) so we just pass the r attribute for both. - let start_radius = gr.r.get() as f32; + + let start_center: vello::kurbo::Point = (gr.fx, gr.fy).into(); + let end_center: vello::kurbo::Point = (gr.cx, gr.cy).into(); + let start_radius = 0_f32; let end_radius = gr.r.get() as f32; - let gradient = vello::peniko::Gradient::new_two_point_radial( - (gr.fx, gr.fy), - start_radius, - (gr.cx, gr.cy), - end_radius, - ) - .with_stops(stops.as_slice()); let transform = Affine::new([ gr.transform.a, gr.transform.b, @@ -242,7 +242,14 @@ fn paint_to_brush(paint: &usvg::Paint, opacity: usvg::Opacity) -> Option<(Brush, gr.transform.e, gr.transform.f, ]); - Some((Brush::Gradient(gradient), transform)) + let gradient = vello::peniko::Gradient::new_two_point_radial( + start_center, + start_radius, + end_center, + end_radius, + ) + .with_stops(stops.as_slice()); + Some((Brush::Gradient(gradient), Some(transform))) } usvg::Paint::Pattern(_) => None, } From ed62181efec777713f4bffd3f38f21f3598b7659 Mon Sep 17 00:00:00 2001 From: "Spencer C. Imbleau" Date: Sat, 2 Sep 2023 12:08:38 -0400 Subject: [PATCH 5/6] just return Affine::IDENTITY instead of Option --- integrations/vello_svg/src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/integrations/vello_svg/src/lib.rs b/integrations/vello_svg/src/lib.rs index a0c22b635..50546d35e 100644 --- a/integrations/vello_svg/src/lib.rs +++ b/integrations/vello_svg/src/lib.rs @@ -120,7 +120,7 @@ pub fn render_tree_with Result<(), E Fill::NonZero, transform, &brush, - brush_transform, + Some(brush_transform), &local_path, ); } else { @@ -136,7 +136,7 @@ pub fn render_tree_with Result<(), E &Stroke::new(stroke.width.get() as f32), transform, &brush, - brush_transform, + Some(brush_transform), &local_path, ); } else { @@ -176,7 +176,7 @@ pub fn default_error_handler(sb: &mut SceneBuilder, node: &usvg::Node) -> Result Ok(()) } -fn paint_to_brush(paint: &usvg::Paint, opacity: usvg::Opacity) -> Option<(Brush, Option)> { +fn paint_to_brush(paint: &usvg::Paint, opacity: usvg::Opacity) -> Option<(Brush, Affine)> { match paint { usvg::Paint::Color(color) => Some(( Brush::Solid(Color::rgba8( @@ -185,7 +185,7 @@ fn paint_to_brush(paint: &usvg::Paint, opacity: usvg::Opacity) -> Option<(Brush, color.blue, opacity.to_u8(), )), - None, + Affine::IDENTITY, )), usvg::Paint::LinearGradient(gr) => { let stops: Vec = gr @@ -213,7 +213,7 @@ fn paint_to_brush(paint: &usvg::Paint, opacity: usvg::Opacity) -> Option<(Brush, ]); let gradient = vello::peniko::Gradient::new_linear(start, end).with_stops(stops.as_slice()); - Some((Brush::Gradient(gradient), Some(transform))) + Some((Brush::Gradient(gradient), transform)) } usvg::Paint::RadialGradient(gr) => { let stops: Vec = gr @@ -249,7 +249,7 @@ fn paint_to_brush(paint: &usvg::Paint, opacity: usvg::Opacity) -> Option<(Brush, end_radius, ) .with_stops(stops.as_slice()); - Some((Brush::Gradient(gradient), Some(transform))) + Some((Brush::Gradient(gradient), transform)) } usvg::Paint::Pattern(_) => None, } From e81d8f5caa72041dddc0b9af81994ec190e1ba2d Mon Sep 17 00:00:00 2001 From: "Spencer C. Imbleau" Date: Mon, 11 Sep 2023 16:34:53 -0400 Subject: [PATCH 6/6] fix: typo with opacity --- integrations/vello_svg/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/integrations/vello_svg/src/lib.rs b/integrations/vello_svg/src/lib.rs index 50546d35e..b19f5d241 100644 --- a/integrations/vello_svg/src/lib.rs +++ b/integrations/vello_svg/src/lib.rs @@ -196,8 +196,8 @@ fn paint_to_brush(paint: &usvg::Paint, opacity: usvg::Opacity) -> Option<(Brush, cstop.color.r = stop.color.red; cstop.color.g = stop.color.green; cstop.color.b = stop.color.blue; - cstop.color.a = stop.opacity.to_u8(); - cstop.offset = (stop.offset * opacity).get() as f32; + cstop.color.a = (stop.opacity * opacity).to_u8(); + cstop.offset = stop.offset.get() as f32; cstop }) .collect(); @@ -224,8 +224,8 @@ fn paint_to_brush(paint: &usvg::Paint, opacity: usvg::Opacity) -> Option<(Brush, cstop.color.r = stop.color.red; cstop.color.g = stop.color.green; cstop.color.b = stop.color.blue; - cstop.color.a = stop.opacity.to_u8(); - cstop.offset = (stop.offset * opacity).get() as f32; + cstop.color.a = (stop.opacity * opacity).to_u8(); + cstop.offset = stop.offset.get() as f32; cstop }) .collect();