Skip to content

Commit c5f2a4f

Browse files
committed
Fixed transforms on overlay strokes using stroke transform
1 parent b1d47b8 commit c5f2a4f

5 files changed

Lines changed: 98 additions & 83 deletions

File tree

editor/src/messages/portfolio/document/overlays/utility_types_web.rs

Lines changed: 58 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ use core::f64::consts::{FRAC_PI_2, PI, TAU};
1212
use glam::{DAffine2, DVec2};
1313
use graphene_std::Color;
1414
use graphene_std::math::quad::Quad;
15+
use graphene_std::raster::curve;
1516
use graphene_std::subpath::Subpath;
1617
use graphene_std::vector::click_target::ClickTargetType;
1718
use graphene_std::vector::misc::{dvec2_to_point, point_to_dvec2};
19+
use graphene_std::vector::stroke::DashLengthsInput;
1820
use graphene_std::vector::style::Stroke;
1921
use graphene_std::vector::{PointId, SegmentId, Vector};
2022
use kurbo::{self, Affine, CubicBez, ParamCurve, PathSeg};
@@ -907,45 +909,50 @@ impl OverlayContext {
907909
self.end_dpi_aware_transform();
908910
}
909911

910-
pub fn draw_path_from_subpaths(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, stroke_transform: Option<DAffine2>) {
911-
self.start_dpi_aware_transform();
912-
913-
// let a = stroke_transform.matrix2.x_axis.x;
914-
// let b = stroke_transform.matrix2.y_axis.x;
915-
// let c = stroke_transform.matrix2.x_axis.y;
916-
// let d = stroke_transform.matrix2.y_axis.y;
917-
// let e = stroke_transform.translation.x;
918-
// let f = stroke_transform.translation.y;
912+
pub fn draw_path_from_subpaths(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, stroke_transform: DAffine2) {
913+
// self.render_context.save();
914+
// self.start_dpi_aware_transform();
915+
916+
// let a = transform.matrix2.x_axis.x;
917+
// let b = transform.matrix2.y_axis.x;
918+
// let c = transform.matrix2.x_axis.y;
919+
// let d = transform.matrix2.y_axis.y;
920+
// let e = transform.translation.x;
921+
// let f = transform.translation.y;
922+
// self.render_context.transform(a, b, c, d, e, f);
923+
// self.render_context.set_transform(a, b, c, d, e, f);
919924
self.render_context.begin_path();
920925
for subpath in subpaths {
921-
let mut subpath = subpath.borrow().clone();
922-
let mut curves = subpath.iter().peekable();
926+
let subpath = subpath.borrow().clone();
927+
let mut bezpath = subpath.to_bezpath();
928+
bezpath.apply_affine(Affine::new((stroke_transform).to_cols_array()));
929+
let mut curves = bezpath.segments().peekable();
923930

924931
let Some(&first) = curves.peek() else {
925932
continue;
926933
};
927934

928-
let start_point = transform.transform_point2(point_to_dvec2(first.start()));
935+
let start_point = point_to_dvec2(first.start());
929936
self.render_context.move_to(start_point.x, start_point.y);
930937

931938
for curve in curves {
932939
match curve {
933940
PathSeg::Line(line) => {
934-
let a = transform.transform_point2(point_to_dvec2(line.p1));
941+
let a = point_to_dvec2(line.p1);
935942
let a = a.round() - DVec2::splat(0.5);
936943
self.render_context.line_to(a.x, a.y);
937944
}
938945
PathSeg::Quad(quad_bez) => {
939-
let a = transform.transform_point2(point_to_dvec2(quad_bez.p1));
940-
let b = transform.transform_point2(point_to_dvec2(quad_bez.p2));
946+
let a = point_to_dvec2(quad_bez.p1);
947+
let b = point_to_dvec2(quad_bez.p2);
941948
let a = a.round() - DVec2::splat(0.5);
942949
let b = b.round() - DVec2::splat(0.5);
943950
self.render_context.quadratic_curve_to(a.x, a.y, b.x, b.y);
944951
}
945952
PathSeg::Cubic(cubic_bez) => {
946-
let a = transform.transform_point2(point_to_dvec2(cubic_bez.p1));
947-
let b = transform.transform_point2(point_to_dvec2(cubic_bez.p2));
948-
let c = transform.transform_point2(point_to_dvec2(cubic_bez.p3));
953+
let a = point_to_dvec2(cubic_bez.p1);
954+
let b = point_to_dvec2(cubic_bez.p2);
955+
let c = point_to_dvec2(cubic_bez.p3);
949956
let a = a.round() - DVec2::splat(0.5);
950957
let b = b.round() - DVec2::splat(0.5);
951958
let c = c.round() - DVec2::splat(0.5);
@@ -959,11 +966,14 @@ impl OverlayContext {
959966
}
960967
}
961968

962-
self.end_dpi_aware_transform();
969+
// self.end_dpi_aware_transform();
970+
// self.render_context.restore();
963971
}
964972

965973
/// Used by the Select tool to outline a path or a free point when selected or hovered.
966974
pub fn outline(&mut self, target_types: impl Iterator<Item = impl Borrow<ClickTargetType>>, transform: DAffine2, color: Option<&str>) {
975+
self.render_context.save();
976+
self.start_dpi_aware_transform();
967977
let mut subpaths: Vec<Subpath<PointId>> = vec![];
968978

969979
target_types.for_each(|target_type| match target_type.borrow() {
@@ -974,13 +984,16 @@ impl OverlayContext {
974984
});
975985

976986
if !subpaths.is_empty() {
977-
self.draw_path_from_subpaths(subpaths.iter(), transform, None);
987+
// TODO: Modify stroke_transform to take note of this
988+
self.draw_path_from_subpaths(subpaths.iter(), transform);
978989

979990
let color = color.unwrap_or(COLOR_OVERLAY_BLUE);
980991
self.render_context.set_stroke_style_str(color);
981992
self.render_context.set_line_width(1.);
982993
self.render_context.stroke();
983994
}
995+
self.end_dpi_aware_transform();
996+
self.render_context.restore();
984997
}
985998

986999
/// Default canvas pattern used for filling stroke or fill of a path.
@@ -1029,63 +1042,60 @@ impl OverlayContext {
10291042
stroke_width: Option<f64>,
10301043
) {
10311044
self.render_context.save();
1032-
self.render_context.set_line_width(stroke_width.unwrap_or(1.));
1033-
self.draw_path_from_subpaths(subpaths, transform, Some(stroke_transform));
1045+
self.start_dpi_aware_transform();
10341046

10351047
if with_pattern {
10361048
self.render_context.set_fill_style_canvas_pattern(&self.fill_canvas_pattern(color));
10371049
} else {
10381050
let color_str = format!("#{:?}", color.to_rgba_hex_srgb());
10391051
self.render_context.set_fill_style_str(&color_str.as_str());
10401052
}
1053+
// let stroke_transform = Some(stroke_transform).filter(|transform| transform.matrix2.determinant() != 0.).unwrap_or(DAffine2::IDENTITY);
1054+
let a = transform.matrix2.x_axis.x;
1055+
let b = transform.matrix2.y_axis.x;
1056+
let c = transform.matrix2.x_axis.y;
1057+
let d = transform.matrix2.y_axis.y;
1058+
let e = transform.translation.x;
1059+
let f = transform.translation.y;
1060+
self.render_context.transform(a, b, c, d, e, f);
1061+
self.draw_path_from_subpaths(subpaths, stroke_transform);
10411062
self.render_context.fill();
10421063

10431064
// Make the stroke transparent and erase the fill area overlapping the stroke.
10441065
if clear_stroke_part {
1045-
// self.render_context.save();
1046-
// let stroke_transform = Some(stroke_transform).filter(|transform| transform.matrix2.determinant() != 0.).unwrap_or(DAffine2::IDENTITY);
1047-
// let a = stroke_transform.matrix2.x_axis.x;
1048-
// let b = stroke_transform.matrix2.y_axis.x;
1049-
// let c = stroke_transform.matrix2.x_axis.y;
1050-
// let d = stroke_transform.matrix2.y_axis.y;
1051-
// let e = stroke_transform.translation.x;
1052-
// let f = stroke_transform.translation.y;
1053-
// self.render_context.set_transform(a, b, c, d, e, f);
1054-
1066+
self.render_context.set_line_width(stroke_width.unwrap_or(1.));
10551067
self.render_context.set_global_composite_operation("destination-out").expect("Failed to set global composite operation");
10561068
self.render_context.set_stroke_style_str(&"#000000");
10571069
self.render_context.stroke();
1058-
// self.render_context.restore();
10591070
}
10601071

1072+
self.end_dpi_aware_transform();
10611073
self.render_context.restore();
10621074
}
10631075

10641076
pub fn fill_stroke(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, overlay_stroke: &Stroke) {
10651077
self.render_context.save();
1066-
1067-
// debug!("overlay_stroke.weight * ptz.zoom(): {:?}", overlay_stroke.weight);
1068-
self.render_context.set_line_width(overlay_stroke.weight);
1069-
self.draw_path_from_subpaths(subpaths, transform, Some(overlay_stroke.transform));
1070-
1071-
// self.render_context.save();
1072-
// let stroke_transform = Some(overlay_stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.).unwrap_or(DAffine2::IDENTITY);
1073-
// let a = stroke_transform.matrix2.x_axis.x;
1074-
// let b = stroke_transform.matrix2.y_axis.x;
1075-
// let c = stroke_transform.matrix2.x_axis.y;
1076-
// let d = stroke_transform.matrix2.y_axis.y;
1077-
// let e = stroke_transform.translation.x;
1078-
// let f = stroke_transform.translation.y;
1079-
// self.render_context.set_transform(a, b, c, d, e, f);
1078+
self.start_dpi_aware_transform();
10801079

10811080
self.render_context
10821081
.set_stroke_style_canvas_pattern(&self.fill_canvas_pattern(&overlay_stroke.color.expect("Color should be set for fill_stroke()")));
1082+
self.render_context.set_line_width(overlay_stroke.weight);
10831083
self.render_context.set_line_cap(overlay_stroke.cap.html_canvas_name().as_str());
10841084
self.render_context.set_line_join(overlay_stroke.join.html_canvas_name().as_str());
10851085
self.render_context.set_miter_limit(overlay_stroke.join_miter_limit);
1086+
// let stroke_transform = Some(overlay_stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.).unwrap_or(DAffine2::IDENTITY);
1087+
// let stroke_transform = overlay_stroke.transform;
1088+
let a = transform.matrix2.x_axis.x;
1089+
let b = transform.matrix2.y_axis.x;
1090+
let c = transform.matrix2.x_axis.y;
1091+
let d = transform.matrix2.y_axis.y;
1092+
let e = transform.translation.x;
1093+
let f = transform.translation.y;
1094+
self.render_context.transform(a, b, c, d, e, f);
1095+
self.draw_path_from_subpaths(subpaths, overlay_stroke.transform);
10861096
self.render_context.stroke();
1087-
// self.render_context.restore();
10881097

1098+
self.end_dpi_aware_transform();
10891099
self.render_context.restore();
10901100
}
10911101

editor/src/messages/portfolio/document/utility_types/document_metadata.rs

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -87,28 +87,6 @@ impl DocumentMetadata {
8787
footprint * local_transform
8888
}
8989

90-
pub fn transform_to_viewport_with_stroke_transform(&self, layer: LayerNodeIdentifier, vector: Vector) -> DAffine2 {
91-
// We're not allowed to convert the root parent to a node id
92-
if layer == LayerNodeIdentifier::ROOT_PARENT {
93-
return self.document_to_viewport;
94-
}
95-
96-
let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport);
97-
let local_transform_for_layer = self.local_transforms.get(&layer.to_node()).copied().unwrap_or_default();
98-
99-
let has_real_stroke = vector.style.stroke().filter(|stroke| stroke.weight() > 0.);
100-
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
101-
if let Some(stroke_transform) = set_stroke_transform {
102-
// Both upstream and downstream (from stroke node) contains stroke transforms
103-
// which shouldn't be the case. This branch should only execute for downstreamed.
104-
footprint * stroke_transform * local_transform_for_layer * stroke_transform.inverse()
105-
} else {
106-
// This branch is not executed on upstream or downstream transforms (from stroke node) layers.
107-
// This branch should only execute for upstreamed.
108-
footprint * local_transform_for_layer
109-
}
110-
}
111-
11290
pub fn transform_to_viewport_if_feeds(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {
11391
// We're not allowed to convert the root parent to a node id
11492
if layer == LayerNodeIdentifier::ROOT_PARENT {

editor/src/messages/tool/tool_messages/fill_tool.rs

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use graph_craft::document::value::TaggedValue;
66
use graphene_std::NodeInputDecleration;
77
use graphene_std::subpath::Subpath;
88
use graphene_std::vector::PointId;
9-
use graphene_std::vector::stroke::{CapInput, JoinInput, MiterLimitInput};
10-
use graphene_std::vector::style::{Fill, Stroke, StrokeCap, StrokeJoin};
9+
use graphene_std::vector::stroke::{AlignInput, CapInput, JoinInput, MiterLimitInput, PaintOrderInput};
10+
use graphene_std::vector::style::{Fill, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
1111
use kurbo::ParamCurveNearest;
1212

1313
#[derive(Default, ExtractField)]
@@ -155,11 +155,18 @@ impl Fsm for FillToolFsmState {
155155
// Stroke
156156
let stroke_node = graph_layer.upstream_node_id_from_name(&STROKE_ID);
157157
let stroke_exists_and_visible = stroke_node.is_some_and(|stroke| document.network_interface.is_visible(&stroke, &[]));
158-
159158
let stroke = vector_data.style.stroke();
159+
160+
let has_real_stroke = stroke.filter(|stroke| stroke.weight() > 0.);
161+
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
162+
let applied_stroke_transform = set_stroke_transform.unwrap_or(document.metadata().transform_to_viewport(layer));
163+
let element_transform = set_stroke_transform
164+
.map(|stroke_transform| document.metadata().transform_to_viewport(layer) * stroke_transform.inverse())
165+
.unwrap_or(DAffine2::IDENTITY);
166+
160167
let stroke_width = get_stroke_width(layer, &document.network_interface).unwrap_or(1.0);
161-
let zoom = document.document_ptz.zoom();
162-
let modified_stroke_width = stroke_width * zoom;
168+
let zoom: f64 = document.document_ptz.zoom();
169+
let modified_stroke_width = stroke_width;
163170
let close_to_stroke = subpaths.any(|subpath| close_to_subpath(input.mouse.position, subpath, stroke_width, zoom, document.metadata().transform_to_viewport(layer)));
164171

165172
// Fill
@@ -170,23 +177,36 @@ impl Fsm for FillToolFsmState {
170177
if stroke_exists_and_visible && close_to_stroke {
171178
let overlay_stroke = || {
172179
let mut overlay_stroke = Stroke::new(Some(preview_color), modified_stroke_width);
173-
overlay_stroke.transform = DAffine2::IDENTITY;
180+
overlay_stroke.transform = applied_stroke_transform;
181+
182+
let align = graph_layer.find_input(&STROKE_ID, AlignInput::INDEX).unwrap();
183+
overlay_stroke.align = if let TaggedValue::StrokeAlign(align) = align { *align } else { StrokeAlign::default() };
184+
174185
let line_cap = graph_layer.find_input(&STROKE_ID, CapInput::INDEX).unwrap();
175186
overlay_stroke.cap = if let TaggedValue::StrokeCap(line_cap) = line_cap { *line_cap } else { StrokeCap::default() };
187+
176188
let line_join = graph_layer.find_input(&STROKE_ID, JoinInput::INDEX).unwrap();
177189
overlay_stroke.join = if let TaggedValue::StrokeJoin(line_join) = line_join { *line_join } else { StrokeJoin::default() };
190+
178191
let miter_limit = graph_layer.find_input(&STROKE_ID, MiterLimitInput::INDEX).unwrap();
179192
overlay_stroke.join_miter_limit = if let TaggedValue::F64(miter_limit) = miter_limit { *miter_limit } else { f64::default() };
180193

194+
let paint_order = graph_layer.find_input(&STROKE_ID, PaintOrderInput::INDEX).unwrap();
195+
overlay_stroke.paint_order = if let TaggedValue::PaintOrder(paint_order) = paint_order {
196+
*paint_order
197+
} else {
198+
PaintOrder::default()
199+
};
200+
181201
overlay_stroke
182202
};
183203

184-
overlay_context.fill_stroke(subpaths, document.metadata().transform_to_viewport_with_stroke_transform(layer, vector_data.clone()), &overlay_stroke());
204+
overlay_context.fill_stroke(subpaths, element_transform, &overlay_stroke());
185205
} else if fill_exists_and_visible {
186206
overlay_context.fill_path(
187207
subpaths,
188-
document.metadata().transform_to_viewport_with_stroke_transform(layer, vector_data.clone()),
189-
DAffine2::IDENTITY,
208+
element_transform,
209+
applied_stroke_transform,
190210
&preview_color,
191211
true,
192212
stroke_exists_and_visible,

node-graph/libraries/rendering/src/renderer.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -727,10 +727,11 @@ impl Render for Table<Vector> {
727727
for row in self.iter() {
728728
let vector = &row.element;
729729
// Only consider strokes with non-zero weight, since default strokes with zero weight would prevent assigning the correct stroke transform
730-
let has_real_stroke = vector.style.stroke().filter(|stroke| stroke.weight() > 0.);
730+
let has_real_stroke = vector.style.stroke().filter(|stroke| stroke.weight() > 1.);
731731
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
732732
let applied_stroke_transform = set_stroke_transform.unwrap_or(*row.transform);
733733
let applied_stroke_transform = render_params.alignment_parent_transform.unwrap_or(applied_stroke_transform);
734+
734735
let element_transform = set_stroke_transform.map(|stroke_transform| *row.transform * stroke_transform.inverse());
735736
let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY);
736737
let layer_bounds = vector.bounding_box().unwrap_or_default();
@@ -742,7 +743,7 @@ impl Render for Table<Vector> {
742743
let mut path = String::new();
743744

744745
for mut bezpath in row.element.stroke_bezpath_iter() {
745-
// Only affects upstream-transformed (from stroke node) layers with row.transform
746+
// Only seems to affect upstream-transformed (from stroke node) layers ~~with row.transform~~
746747
bezpath.apply_affine(Affine::new(applied_stroke_transform.to_cols_array()));
747748
path.push_str(bezpath.to_svg().as_str());
748749
}
@@ -761,6 +762,7 @@ impl Render for Table<Vector> {
761762
let wants_stroke_below = vector.style.stroke().map(|s| s.paint_order) == Some(PaintOrder::StrokeBelow);
762763

763764
if needs_separate_alignment_fill && !wants_stroke_below {
765+
log::info!("Entering needs_separate_alignment_fill && !wants_stroke_below");
764766
render.leaf_tag("path", |attributes| {
765767
attributes.push("d", path.clone());
766768
let matrix = format_transform_matrix(element_transform);
@@ -799,6 +801,7 @@ impl Render for Table<Vector> {
799801
});
800802

801803
if vector.is_branching() {
804+
log::info!("Entering vector.is_branching()");
802805
for mut face_path in vector.construct_faces().filter(|face| !(face.area() < 0.0)) {
803806
face_path.apply_affine(Affine::new(applied_stroke_transform.to_cols_array()));
804807

@@ -826,15 +829,16 @@ impl Render for Table<Vector> {
826829

827830
render.leaf_tag("path", |attributes| {
828831
attributes.push("d", path.clone());
829-
// Only affects layers with downstream-transformed layers (from stroke node) with row.transform*stroke_transform.inverse()
830-
// and affect layers with upstream-transformed (from stroke node) layers with IDENTITY
832+
// Only seem to affect layers with downstream-transformed layers (from stroke node) with ~~row.transform*stroke_transform.inverse()~~
833+
// and affect layers with upstream-transformed (from stroke node) layers ~~with IDENTITY~~
831834
let matrix = format_transform_matrix(element_transform);
832835
if !matrix.is_empty() {
833836
attributes.push("transform", matrix);
834837
}
835838

836839
let defs = &mut attributes.0.svg_defs;
837840
if let Some((ref id, mask_type, ref vector_row)) = push_id {
841+
log::info!("Entering Some(p) = push_id");
838842
let mut svg = SvgRender::new();
839843
vector_row.render_svg(&mut svg, &render_params.for_alignment(applied_stroke_transform));
840844
let stroke = row.element.style.stroke().unwrap();
@@ -868,6 +872,7 @@ impl Render for Table<Vector> {
868872
}
869873

870874
let fill_and_stroke = style.render(defs, element_transform, applied_stroke_transform, bounds_matrix, transformed_bounds_matrix, &render_params);
875+
// log::info!("file_and_stroke: {:?}", fill_and_stroke);
871876

872877
if let Some((id, mask_type, _)) = push_id {
873878
let selector = format!("url(#{id})");
@@ -888,6 +893,7 @@ impl Render for Table<Vector> {
888893

889894
// When splitting passes and stroke is below, draw the fill after the stroke.
890895
if needs_separate_alignment_fill && wants_stroke_below {
896+
log::info!("Entering needs_separate_alignment_fill && wants_stroke_below");
891897
render.leaf_tag("path", |attributes| {
892898
attributes.push("d", path);
893899
let matrix = format_transform_matrix(element_transform);

node-graph/nodes/gstd/src/render_node.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ async fn render_intermediate<'a: 'n, T: 'static + Render + WasmNotSend + Send +
5757

5858
let footprint = Footprint::default();
5959
let mut metadata = RenderMetadata::default();
60+
// All the metadata of the upstream Graphic(s) is collected here
6061
data.collect_metadata(&mut metadata, footprint, None);
6162
let contains_artboard = data.contains_artboard();
6263

0 commit comments

Comments
 (0)