From 1f2b09565ff3f170a4033a82881aef68c2e2dfb4 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Sat, 2 Mar 2024 14:10:59 -0500 Subject: [PATCH] Push group clipping down, support rounded rectangle example --- .../vega-scenegraphs/clip/bar_rounded.png | Bin 0 -> 117558 bytes .../vega-scenegraphs/clip/bar_rounded.sg.json | 1903 +++++++++++++++++ .../vega-specs/clip/bar_rounded.vg.json | 179 ++ avenger-vega/src/marks/arc.rs | 4 +- avenger-vega/src/marks/area.rs | 4 +- avenger-vega/src/marks/group.rs | 57 +- avenger-vega/src/marks/image.rs | 4 +- avenger-vega/src/marks/line.rs | 4 +- avenger-vega/src/marks/path.rs | 4 +- avenger-vega/src/marks/rect.rs | 4 +- avenger-vega/src/marks/rule.rs | 4 +- avenger-vega/src/marks/shape.rs | 4 +- avenger-vega/src/marks/symbol.rs | 4 +- avenger-vega/src/marks/text.rs | 4 +- avenger-vega/src/marks/trail.rs | 4 +- avenger-vega/src/scene_graph.rs | 2 +- avenger-wgpu/src/canvas.rs | 13 +- avenger-wgpu/tests/test_image_baselines.rs | 1 + 18 files changed, 2158 insertions(+), 41 deletions(-) create mode 100644 avenger-vega-test-data/vega-scenegraphs/clip/bar_rounded.png create mode 100644 avenger-vega-test-data/vega-scenegraphs/clip/bar_rounded.sg.json create mode 100644 avenger-vega-test-data/vega-specs/clip/bar_rounded.vg.json diff --git a/avenger-vega-test-data/vega-scenegraphs/clip/bar_rounded.png b/avenger-vega-test-data/vega-scenegraphs/clip/bar_rounded.png new file mode 100644 index 0000000000000000000000000000000000000000..12ea28a76e73d83cdbfcd68498e2eff5585d4368 GIT binary patch literal 117558 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 { - pub fn to_scene_graph(&self) -> Result { + pub fn to_scene_graph(&self, force_clip: bool) -> Result { // Init mark with scalar defaults let mut mark = ArcMark { - clip: self.clip, + clip: self.clip || force_clip, zindex: self.zindex, ..Default::default() }; diff --git a/avenger-vega/src/marks/area.rs b/avenger-vega/src/marks/area.rs index b5f07f5..3bd359d 100644 --- a/avenger-vega/src/marks/area.rs +++ b/avenger-vega/src/marks/area.rs @@ -29,7 +29,7 @@ pub struct VegaAreaItem { impl VegaMarkItem for VegaAreaItem {} impl VegaMarkContainer { - pub fn to_scene_graph(&self) -> Result { + pub fn to_scene_graph(&self, force_clip: bool) -> Result { // Get shape of first item and use that for all items for now let first = self.items.first(); let stroke_cap = first.and_then(|item| item.stroke_cap).unwrap_or_default(); @@ -61,7 +61,7 @@ impl VegaMarkContainer { } let mut mark = AreaMark { - clip: self.clip, + clip: self.clip || force_clip, zindex: self.zindex, orientation, fill, diff --git a/avenger-vega/src/marks/group.rs b/avenger-vega/src/marks/group.rs index 56eda75..354c271 100644 --- a/avenger-vega/src/marks/group.rs +++ b/avenger-vega/src/marks/group.rs @@ -13,6 +13,7 @@ use serde::{Deserialize, Serialize}; #[serde(rename_all = "camelCase")] pub struct VegaGroupItem { pub items: Vec, + pub clip: Option, pub x: Option, pub y: Option, pub name: Option, @@ -22,6 +23,10 @@ pub struct VegaGroupItem { pub stroke: Option, pub stroke_width: Option, pub corner_radius: Option, + pub corner_radius_top_left: Option, + pub corner_radius_top_right: Option, + pub corner_radius_bottom_left: Option, + pub corner_radius_bottom_right: Option, pub opacity: Option, pub fill_opacity: Option, pub stroke_opacity: Option, @@ -32,49 +37,50 @@ pub struct VegaGroupItem { impl VegaMarkItem for VegaGroupItem {} impl VegaMarkContainer { - pub fn to_scene_graph(&self) -> Result, AvengerVegaError> { + pub fn to_scene_graph(&self, force_clip: bool) -> Result, AvengerVegaError> { let mut groups: Vec = Vec::new(); for group_item in &self.items { + let should_clip = group_item.clip.unwrap_or(false) || force_clip; let mut marks: Vec = Vec::new(); for item in &group_item.items { let item_marks: Vec = match item { VegaMark::Group(mark) => mark - .to_scene_graph()? + .to_scene_graph(should_clip)? .into_iter() .map(SceneMark::Group) .collect(), VegaMark::Rect(mark) => { - vec![mark.to_scene_graph()?] + vec![mark.to_scene_graph(should_clip)?] } VegaMark::Rule(mark) => { - vec![mark.to_scene_graph()?] + vec![mark.to_scene_graph(should_clip)?] } VegaMark::Symbol(mark) => { - vec![mark.to_scene_graph()?] + vec![mark.to_scene_graph(should_clip)?] } VegaMark::Text(mark) => { - vec![mark.to_scene_graph()?] + vec![mark.to_scene_graph(should_clip)?] } VegaMark::Arc(mark) => { - vec![mark.to_scene_graph()?] + vec![mark.to_scene_graph(should_clip)?] } VegaMark::Path(mark) => { - vec![mark.to_scene_graph()?] + vec![mark.to_scene_graph(should_clip)?] } VegaMark::Shape(mark) => { - vec![mark.to_scene_graph()?] + vec![mark.to_scene_graph(should_clip)?] } VegaMark::Line(mark) => { - vec![mark.to_scene_graph()?] + vec![mark.to_scene_graph(should_clip)?] } VegaMark::Area(mark) => { - vec![mark.to_scene_graph()?] + vec![mark.to_scene_graph(should_clip)?] } VegaMark::Trail(mark) => { - vec![mark.to_scene_graph()?] + vec![mark.to_scene_graph(should_clip)?] } VegaMark::Image(mark) => { - vec![mark.to_scene_graph()?] + vec![mark.to_scene_graph(should_clip)?] } }; marks.extend(item_marks); @@ -97,12 +103,33 @@ impl VegaMarkContainer { }; let clip = if let (Some(width), Some(height)) = (group_item.width, group_item.height) { - if let Some(corner_radius) = group_item.corner_radius { + let corner_radius = group_item.corner_radius.unwrap_or(0.0); + let corner_radius_top_left = + group_item.corner_radius_top_left.unwrap_or(corner_radius); + let corner_radius_top_right = + group_item.corner_radius_top_right.unwrap_or(corner_radius); + let corner_radius_bottom_left = group_item + .corner_radius_bottom_left + .unwrap_or(corner_radius); + let corner_radius_bottom_right = group_item + .corner_radius_bottom_right + .unwrap_or(corner_radius); + + if corner_radius_top_left > 0.0 + || corner_radius_top_right > 0.0 + || corner_radius_bottom_left > 0.0 + || corner_radius_bottom_right > 0.0 + { // Rounded rectange path let mut builder = lyon_path::Path::builder(); builder.add_rounded_rectangle( &Box2D::new(Point2D::new(0.0, 0.0), Point2D::new(width, height)), - &BorderRadii::new(corner_radius), + &BorderRadii { + top_left: corner_radius_top_left, + top_right: corner_radius_top_right, + bottom_left: corner_radius_bottom_left, + bottom_right: corner_radius_bottom_right, + }, Winding::Positive, ); Clip::Path(builder.build()) diff --git a/avenger-vega/src/marks/image.rs b/avenger-vega/src/marks/image.rs index 75639ad..72711ed 100644 --- a/avenger-vega/src/marks/image.rs +++ b/avenger-vega/src/marks/image.rs @@ -33,7 +33,7 @@ fn default_true() -> bool { impl VegaMarkItem for VegaImageItem {} impl VegaMarkContainer { - pub fn to_scene_graph(&self) -> Result { + pub fn to_scene_graph(&self, force_clip: bool) -> Result { let name = self .name .clone() @@ -96,7 +96,7 @@ impl VegaMarkContainer { Ok(SceneMark::Image(Box::new(ImageMark { name, - clip: self.clip, + clip: self.clip || force_clip, len: self.items.len() as u32, aspect, smooth, diff --git a/avenger-vega/src/marks/line.rs b/avenger-vega/src/marks/line.rs index 8cca85c..6655db1 100644 --- a/avenger-vega/src/marks/line.rs +++ b/avenger-vega/src/marks/line.rs @@ -24,7 +24,7 @@ pub struct VegaLineItem { impl VegaMarkItem for VegaLineItem {} impl VegaMarkContainer { - pub fn to_scene_graph(&self) -> Result { + pub fn to_scene_graph(&self, force_clip: bool) -> Result { // Get shape of first item and use that for all items for now let first = self.items.first(); let stroke_width = first.and_then(|item| item.stroke_width).unwrap_or(1.0); @@ -47,7 +47,7 @@ impl VegaMarkContainer { } let mut mark = LineMark { - clip: self.clip, + clip: self.clip || force_clip, zindex: self.zindex, stroke, stroke_width, diff --git a/avenger-vega/src/marks/path.rs b/avenger-vega/src/marks/path.rs index 41467e4..5085d30 100644 --- a/avenger-vega/src/marks/path.rs +++ b/avenger-vega/src/marks/path.rs @@ -33,7 +33,7 @@ pub struct VegaPathItem { impl VegaMarkItem for VegaPathItem {} impl VegaMarkContainer { - pub fn to_scene_graph(&self) -> Result { + pub fn to_scene_graph(&self, force_clip: bool) -> Result { // Get shape of first item and use that for all items for now let first = self.items.first(); let first_has_stroke = first.map(|item| item.stroke.is_some()).unwrap_or(false); @@ -49,7 +49,7 @@ impl VegaMarkContainer { // Init mark with scalar defaults let mut mark = PathMark { - clip: self.clip, + clip: self.clip || force_clip, zindex: self.zindex, stroke_cap: first_cap, stroke_join: first_join, diff --git a/avenger-vega/src/marks/rect.rs b/avenger-vega/src/marks/rect.rs index 9fd0cb6..fce192e 100644 --- a/avenger-vega/src/marks/rect.rs +++ b/avenger-vega/src/marks/rect.rs @@ -28,9 +28,9 @@ pub struct VegaRectItem { impl VegaMarkItem for VegaRectItem {} impl VegaMarkContainer { - pub fn to_scene_graph(&self) -> Result { + pub fn to_scene_graph(&self, force_clip: bool) -> Result { let mut mark = RectMark { - clip: self.clip, + clip: self.clip || force_clip, zindex: self.zindex, ..Default::default() }; diff --git a/avenger-vega/src/marks/rule.rs b/avenger-vega/src/marks/rule.rs index 503e708..21ef98a 100644 --- a/avenger-vega/src/marks/rule.rs +++ b/avenger-vega/src/marks/rule.rs @@ -25,10 +25,10 @@ pub struct VegaRuleItem { impl VegaMarkItem for VegaRuleItem {} impl VegaMarkContainer { - pub fn to_scene_graph(&self) -> Result { + pub fn to_scene_graph(&self, force_clip: bool) -> Result { // Init mark with scalar defaults let mut mark = RuleMark { - clip: self.clip, + clip: self.clip || force_clip, zindex: self.zindex, ..Default::default() }; diff --git a/avenger-vega/src/marks/shape.rs b/avenger-vega/src/marks/shape.rs index 7d6b142..aac8c64 100644 --- a/avenger-vega/src/marks/shape.rs +++ b/avenger-vega/src/marks/shape.rs @@ -28,7 +28,7 @@ pub struct VegaShapeItem { impl VegaMarkItem for VegaShapeItem {} impl VegaMarkContainer { - pub fn to_scene_graph(&self) -> Result { + pub fn to_scene_graph(&self, force_clip: bool) -> Result { // Get shape of first item and use that for all items for now let first = self.items.first(); let first_has_stroke = first.map(|item| item.stroke.is_some()).unwrap_or(false); @@ -44,7 +44,7 @@ impl VegaMarkContainer { // Init mark with scalar defaults let mut mark = PathMark { - clip: self.clip, + clip: self.clip || force_clip, zindex: self.zindex, stroke_cap: first_cap, stroke_join: first_join, diff --git a/avenger-vega/src/marks/symbol.rs b/avenger-vega/src/marks/symbol.rs index 686e33d..b17d9f5 100644 --- a/avenger-vega/src/marks/symbol.rs +++ b/avenger-vega/src/marks/symbol.rs @@ -35,7 +35,7 @@ pub struct VegaSymbolItem { impl VegaMarkItem for VegaSymbolItem {} impl VegaMarkContainer { - pub fn to_scene_graph(&self) -> Result { + pub fn to_scene_graph(&self, force_clip: bool) -> Result { // Get shape of first item and use that for all items for now let first = self.items.first(); let first_shape = first @@ -108,7 +108,7 @@ impl VegaMarkContainer { // Init mark with scalar defaults let mut mark = SymbolMark { stroke_width, - clip: self.clip, + clip: self.clip || force_clip, zindex: self.zindex, ..Default::default() }; diff --git a/avenger-vega/src/marks/text.rs b/avenger-vega/src/marks/text.rs index ad44971..9857077 100644 --- a/avenger-vega/src/marks/text.rs +++ b/avenger-vega/src/marks/text.rs @@ -38,10 +38,10 @@ pub struct VegaTextItem { impl VegaMarkItem for VegaTextItem {} impl VegaMarkContainer { - pub fn to_scene_graph(&self) -> Result { + pub fn to_scene_graph(&self, force_clip: bool) -> Result { // Init mark with scalar defaults let mut mark = TextMark { - clip: self.clip, + clip: self.clip || force_clip, zindex: self.zindex, ..Default::default() }; diff --git a/avenger-vega/src/marks/trail.rs b/avenger-vega/src/marks/trail.rs index 8ed5f90..f565e77 100644 --- a/avenger-vega/src/marks/trail.rs +++ b/avenger-vega/src/marks/trail.rs @@ -21,7 +21,7 @@ pub struct VegaTrailItem { impl VegaMarkItem for VegaTrailItem {} impl VegaMarkContainer { - pub fn to_scene_graph(&self) -> Result { + pub fn to_scene_graph(&self, force_clip: bool) -> Result { // Get shape of first item and use that for all items for now let first = self.items.first(); let mut gradients = Vec::::new(); @@ -37,7 +37,7 @@ impl VegaMarkContainer { } let mut mark = TrailMark { - clip: self.clip, + clip: self.clip || force_clip, zindex: self.zindex, gradients, stroke, diff --git a/avenger-vega/src/scene_graph.rs b/avenger-vega/src/scene_graph.rs index f8ecc1b..b42f7b7 100644 --- a/avenger-vega/src/scene_graph.rs +++ b/avenger-vega/src/scene_graph.rs @@ -16,7 +16,7 @@ impl VegaSceneGraph { #[tracing::instrument(skip_all)] pub fn to_scene_graph(&self) -> Result { Ok(SceneGraph { - groups: self.scenegraph.to_scene_graph()?, + groups: self.scenegraph.to_scene_graph(false)?, width: self.width, height: self.height, origin: self.origin, diff --git a/avenger-wgpu/src/canvas.rs b/avenger-wgpu/src/canvas.rs index 51b1d62..aae464f 100644 --- a/avenger-wgpu/src/canvas.rs +++ b/avenger-wgpu/src/canvas.rs @@ -206,6 +206,7 @@ pub trait Canvas { &mut self, group: &SceneGroup, parent_origin: [f32; 2], + parent_clip: &Clip, ) -> Result<(), AvengerWgpuError> { // Maybe add rect around group boundary if let Some(rect) = group.make_path_mark() { @@ -224,7 +225,13 @@ pub trait Canvas { ]; // Compute new clip - let clip = group.clip.translate(origin[0], origin[1]); + let clip = if let Clip::None = group.clip { + // No clip defined for this group, propagate parent clip down + parent_clip.clone() + } else { + // Translate clip to absolute coordinates + group.clip.translate(origin[0], origin[1]) + }; for mark_ind in indices { let mark = &group.marks[mark_ind]; @@ -260,7 +267,7 @@ pub trait Canvas { self.add_image_mark(mark, origin, &clip)?; } SceneMark::Group(group) => { - self.add_group_mark(group, origin)?; + self.add_group_mark(group, origin, &clip)?; } } } @@ -283,7 +290,7 @@ pub trait Canvas { for group_ind in &indices { let group = &scene_graph.groups[*group_ind]; - self.add_group_mark(group, scene_graph.origin)?; + self.add_group_mark(group, scene_graph.origin, &Clip::None)?; } Ok(()) diff --git a/avenger-wgpu/tests/test_image_baselines.rs b/avenger-wgpu/tests/test_image_baselines.rs index 944f202..24b3037 100644 --- a/avenger-wgpu/tests/test_image_baselines.rs +++ b/avenger-wgpu/tests/test_image_baselines.rs @@ -180,6 +180,7 @@ mod test_image_baselines { case("clip", "text_clip", 0.02), case("clip", "clip_rounded", 0.0001), case("clip", "text_clip_rounded", 0.02), + case("clip", "bar_rounded", 0.02), )] fn test_image_baseline(category: &str, spec_name: &str, tolerance: f64) { initialize();