Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create outline with "non-scaling-stroke" SVG tag #2437

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ fn apply_usvg_stroke(stroke: &usvg::Stroke, modify_inputs: &mut ModifyInputsCont
},
line_join_miter_limit: stroke.miterlimit().get() as f64,
transform,
non_scaling: false,
})
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2421,7 +2421,6 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
};
let number_input = NumberInput::default().min(0.).disabled(line_join_val != &LineJoin::Miter);
let miter_limit = number_widget(document_node, node_id, miter_limit_index, "Miter Limit", number_input, true);

vec![
color,
LayoutGroup::Row { widgets: weight },
Expand Down
208 changes: 108 additions & 100 deletions node-graph/gcore/src/graphic_element/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,7 @@ pub trait GraphicElementRendered {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams);

#[cfg(feature = "vello")]
fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2, _render_context: &mut RenderContext) {}

fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams);
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]>;

// The upstream click targets for each layer are collected during the render so that they do not have to be calculated for each click detection
Expand Down Expand Up @@ -325,7 +324,7 @@ impl GraphicElementRendered for GraphicGroupTable {
}

#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext) {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
for instance in self.instances() {
let transform = transform * *instance.transform;
let alpha_blending = *instance.alpha_blending;
Expand All @@ -345,7 +344,7 @@ impl GraphicElementRendered for GraphicGroupTable {
}
}

instance.instance.render_to_vello(scene, transform, context);
instance.instance.render_to_vello(scene, transform, context, render_params);

if layer {
scene.pop_layer();
Expand Down Expand Up @@ -461,20 +460,16 @@ impl GraphicElementRendered for VectorDataTable {
}

#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, parent_transform: DAffine2, _: &mut RenderContext) {
fn render_to_vello(&self, scene: &mut Scene, parent_transform: DAffine2, _: &mut RenderContext, render_params: &RenderParams) {
use crate::vector::style::GradientType;
use vello::peniko;

for instance in self.instances() {
let mut layer = false;

let multiplied_transform = parent_transform * *instance.transform;
let set_stroke_transform = instance
.instance
.style
.stroke()
.map(|stroke| stroke.transform)
.filter(|transform| transform.matrix2.determinant() != 0.);
let has_real_stroke = instance.instance.style.stroke().filter(|stroke| stroke.weight() > 0.);
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
let applied_stroke_transform = set_stroke_transform.unwrap_or(multiplied_transform);
let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse());
let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY);
Expand All @@ -495,86 +490,90 @@ impl GraphicElementRendered for VectorDataTable {
for subpath in instance.instance.stroke_bezier_paths() {
subpath.to_vello_path(applied_stroke_transform, &mut path);
}

match instance.instance.style.fill() {
Fill::Solid(color) => {
let fill = peniko::Brush::Solid(peniko::Color::new([color.r(), color.g(), color.b(), color.a()]));
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, None, &path);
}
Fill::Gradient(gradient) => {
let mut stops = peniko::ColorStops::new();
for &(offset, color) in &gradient.stops.0 {
stops.push(peniko::ColorStop {
offset: offset as f32,
color: peniko::color::DynamicColor::from_alpha_color(peniko::Color::new([color.r(), color.g(), color.b(), color.a()])),
});
}
// Compute bounding box of the shape to determine the gradient start and end points
let bounds = instance.instance.nonzero_bounding_box();
let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);

let inverse_parent_transform = (parent_transform.matrix2.determinant() != 0.).then(|| parent_transform.inverse()).unwrap_or_default();
let mod_points = inverse_parent_transform * multiplied_transform * bound_transform;

let start = mod_points.transform_point2(gradient.start);
let end = mod_points.transform_point2(gradient.end);

let fill = peniko::Brush::Gradient(peniko::Gradient {
kind: match gradient.gradient_type {
GradientType::Linear => peniko::GradientKind::Linear {
start: to_point(start),
end: to_point(end),
},
GradientType::Radial => {
let radius = start.distance(end);
peniko::GradientKind::Radial {
start_center: to_point(start),
start_radius: 0.,
end_center: to_point(start),
end_radius: radius as f32,
}
match render_params.view_mode {
ViewMode::Outline => {}
_ => {
match instance.instance.style.fill() {
Fill::Solid(color) => {
let fill = peniko::Brush::Solid(peniko::Color::new([color.r(), color.g(), color.b(), color.a()]));
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, None, &path);
}
Fill::Gradient(gradient) => {
let mut stops = peniko::ColorStops::new();
for &(offset, color) in &gradient.stops.0 {
stops.push(peniko::ColorStop {
offset: offset as f32,
color: peniko::color::DynamicColor::from_alpha_color(peniko::Color::new([color.r(), color.g(), color.b(), color.a()])),
});
}
},
stops,
..Default::default()
});
// Vello does `element_transform * brush_transform` internally. We don't want element_transform to have any impact so we need to left multiply by the inverse.
// This makes the final internal brush transform equal to `parent_transform`, allowing you to stretch a gradient by transforming the parent folder.
let inverse_element_transform = (element_transform.matrix2.determinant() != 0.).then(|| element_transform.inverse()).unwrap_or_default();
let brush_transform = kurbo::Affine::new((inverse_element_transform * parent_transform).to_cols_array());
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, Some(brush_transform), &path);
}
Fill::None => (),
};

if let Some(stroke) = instance.instance.style.stroke() {
let color = match stroke.color {
Some(color) => peniko::Color::new([color.r(), color.g(), color.b(), color.a()]),
None => peniko::Color::TRANSPARENT,
};
use crate::vector::style::{LineCap, LineJoin};
use vello::kurbo::{Cap, Join};
let cap = match stroke.line_cap {
LineCap::Butt => Cap::Butt,
LineCap::Round => Cap::Round,
LineCap::Square => Cap::Square,
};
let join = match stroke.line_join {
LineJoin::Miter => Join::Miter,
LineJoin::Bevel => Join::Bevel,
LineJoin::Round => Join::Round,
};
let stroke = kurbo::Stroke {
width: stroke.weight,
miter_limit: stroke.line_join_miter_limit,
join,
start_cap: cap,
end_cap: cap,
dash_pattern: stroke.dash_lengths.into(),
dash_offset: stroke.dash_offset,
};
if stroke.width > 0. {
scene.stroke(&stroke, kurbo::Affine::new(element_transform.to_cols_array()), color, None, &path);
// Compute bounding box of the shape to determine the gradient start and end points
let bounds = instance.instance.nonzero_bounding_box();
let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);

let inverse_parent_transform = (parent_transform.matrix2.determinant() != 0.).then(|| parent_transform.inverse()).unwrap_or_default();
let mod_points = inverse_parent_transform * multiplied_transform * bound_transform;

let start = mod_points.transform_point2(gradient.start);
let end = mod_points.transform_point2(gradient.end);

let fill = peniko::Brush::Gradient(peniko::Gradient {
kind: match gradient.gradient_type {
GradientType::Linear => peniko::GradientKind::Linear {
start: to_point(start),
end: to_point(end),
},
GradientType::Radial => {
let radius = start.distance(end);
peniko::GradientKind::Radial {
start_center: to_point(start),
start_radius: 0.,
end_center: to_point(start),
end_radius: radius as f32,
}
}
},
stops,
..Default::default()
});
// Vello does `element_transform * brush_transform` internally. We don't want element_transform to have any impact so we need to left multiply by the inverse.
// This makes the final internal brush transform equal to `parent_transform`, allowing you to stretch a gradient by transforming the parent folder.
let inverse_element_transform = (element_transform.matrix2.determinant() != 0.).then(|| element_transform.inverse()).unwrap_or_default();
let brush_transform = kurbo::Affine::new((inverse_element_transform * parent_transform).to_cols_array());
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, Some(brush_transform), &path);
}
Fill::None => (),
};

if let Some(stroke) = instance.instance.style.stroke() {
let color = match stroke.color {
Some(color) => peniko::Color::new([color.r(), color.g(), color.b(), color.a()]),
None => peniko::Color::TRANSPARENT,
};
use crate::vector::style::{LineCap, LineJoin};
use vello::kurbo::{Cap, Join};
let cap = match stroke.line_cap {
LineCap::Butt => Cap::Butt,
LineCap::Round => Cap::Round,
LineCap::Square => Cap::Square,
};
let join = match stroke.line_join {
LineJoin::Miter => Join::Miter,
LineJoin::Bevel => Join::Bevel,
LineJoin::Round => Join::Round,
};
let stroke = kurbo::Stroke {
width: stroke.weight,
miter_limit: stroke.line_join_miter_limit,
join,
start_cap: cap,
end_cap: cap,
dash_pattern: stroke.dash_lengths.into(),
dash_offset: stroke.dash_offset,
};
if stroke.width > 0. {
scene.stroke(&stroke, kurbo::Affine::new(element_transform.to_cols_array()), color, None, &path);
}
}
}
}
if layer {
Expand Down Expand Up @@ -707,7 +706,7 @@ impl GraphicElementRendered for Artboard {
}

#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext) {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
use vello::peniko;

// Render background
Expand All @@ -725,7 +724,7 @@ impl GraphicElementRendered for Artboard {
}
// Since the graphic group's transform is right multiplied in when rendering the graphic group, we just need to right multiply by the offset here.
let child_transform = transform * DAffine2::from_translation(self.location.as_dvec2());
self.graphic_group.render_to_vello(scene, child_transform, context);
self.graphic_group.render_to_vello(scene, child_transform, context, render_params);
if self.clip {
scene.pop_layer();
}
Expand Down Expand Up @@ -772,9 +771,9 @@ impl GraphicElementRendered for ArtboardGroupTable {
}

#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext) {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
for instance in self.instances() {
instance.instance.render_to_vello(scene, transform, context)
instance.instance.render_to_vello(scene, transform, context, render_params);
}
}

Expand Down Expand Up @@ -837,7 +836,7 @@ impl GraphicElementRendered for ImageFrameTable<Color> {
}

#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext) {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext, _render_params: &RenderParams) {
use vello::peniko;

for instance in self.instances() {
Expand Down Expand Up @@ -887,7 +886,7 @@ impl GraphicElementRendered for RasterFrame {
}

#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext) {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams) {
use vello::peniko;

let mut render_stuff = |image: vello::peniko::Image, blend_mode: crate::AlphaBlending| {
Expand Down Expand Up @@ -964,11 +963,11 @@ impl GraphicElementRendered for GraphicElement {
}

#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext) {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
match self {
GraphicElement::VectorData(vector_data) => vector_data.render_to_vello(scene, transform, context),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.render_to_vello(scene, transform, context),
GraphicElement::RasterFrame(raster) => raster.render_to_vello(scene, transform, context),
GraphicElement::VectorData(vector_data) => vector_data.render_to_vello(scene, transform, context, render_params),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.render_to_vello(scene, transform, context, render_params),
GraphicElement::RasterFrame(raster) => raster.render_to_vello(scene, transform, context, render_params),
}
}

Expand Down Expand Up @@ -1051,6 +1050,9 @@ impl<P: Primitive> GraphicElementRendered for P {
fn bounding_box(&self, _transform: DAffine2) -> Option<[DVec2; 2]> {
None
}

#[cfg(feature = "vello")]
fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2, _context: &mut RenderContext, _render_params: &RenderParams) {}
}

impl GraphicElementRendered for Option<Color> {
Expand All @@ -1076,6 +1078,9 @@ impl GraphicElementRendered for Option<Color> {
fn bounding_box(&self, _transform: DAffine2) -> Option<[DVec2; 2]> {
None
}

#[cfg(feature = "vello")]
fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2, _context: &mut RenderContext, _render_params: &RenderParams) {}
}

impl GraphicElementRendered for Vec<Color> {
Expand All @@ -1097,6 +1102,9 @@ impl GraphicElementRendered for Vec<Color> {
fn bounding_box(&self, _transform: DAffine2) -> Option<[DVec2; 2]> {
None
}

#[cfg(feature = "vello")]
fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2, _context: &mut RenderContext, _render_params: &RenderParams) {}
}

#[derive(Debug, Clone, PartialEq, Eq)]
Expand Down
Loading
Loading