Skip to content

Commit f412eb9

Browse files
committed
Fix overlay pattern scaling and ignore StrokeAlign on open subpaths
1 parent 2adac3b commit f412eb9

2 files changed

Lines changed: 91 additions & 73 deletions

File tree

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

Lines changed: 89 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,16 @@ 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;
1615
use graphene_std::subpath::Subpath;
17-
use graphene_std::transform::Transform;
1816
use graphene_std::vector::click_target::ClickTargetType;
1917
use graphene_std::vector::misc::{dvec2_to_point, point_to_dvec2};
20-
use graphene_std::vector::stroke::DashLengthsInput;
2118
use graphene_std::vector::style::{PaintOrder, Stroke, StrokeAlign};
2219
use graphene_std::vector::{PointId, SegmentId, Vector};
20+
use js_sys::{Array, Reflect};
2321
use kurbo::{self, Affine, CubicBez, ParamCurve, PathSeg};
2422
use std::collections::HashMap;
2523
use wasm_bindgen::{JsCast, JsValue};
26-
use web_sys::{DomMatrix, OffscreenCanvas, OffscreenCanvasRenderingContext2d, SvgMatrix};
24+
use web_sys::{CanvasPattern, DomMatrix, OffscreenCanvas, OffscreenCanvasRenderingContext2d};
2725

2826
pub type OverlayProvider = fn(OverlayContext) -> Message;
2927

@@ -910,18 +908,9 @@ impl OverlayContext {
910908
self.end_dpi_aware_transform();
911909
}
912910

913-
pub fn draw_path_from_subpaths(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, stroke_transform: DAffine2) {
914-
// self.render_context.save();
915-
// self.start_dpi_aware_transform();
916-
917-
// let a = transform.matrix2.x_axis.x;
918-
// let b = transform.matrix2.y_axis.x;
919-
// let c = transform.matrix2.x_axis.y;
920-
// let d = transform.matrix2.y_axis.y;
921-
// let e = transform.translation.x;
922-
// let f = transform.translation.y;
923-
// self.render_context.transform(a, b, c, d, e, f);
924-
// self.render_context.set_transform(a, b, c, d, e, f);
911+
pub fn draw_path_from_subpaths(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, stroke_transform: DAffine2) -> bool {
912+
// Subpaths on a layer is considered "closed" only if all subpaths are closed.
913+
let mut is_closed = true;
925914
self.render_context.begin_path();
926915
for subpath in subpaths {
927916
let subpath = subpath.borrow().clone();
@@ -964,11 +953,11 @@ impl OverlayContext {
964953

965954
if subpath.closed() {
966955
self.render_context.close_path();
956+
} else {
957+
is_closed = false;
967958
}
968959
}
969-
970-
// self.end_dpi_aware_transform();
971-
// self.render_context.restore();
960+
return is_closed;
972961
}
973962

974963
/// Used by the Select tool to outline a path or a free point when selected or hovered.
@@ -998,7 +987,7 @@ impl OverlayContext {
998987
}
999988

1000989
/// Default canvas pattern used for filling stroke or fill of a path.
1001-
fn fill_canvas_pattern(&self, color: &Color, transform: DAffine2) -> web_sys::CanvasPattern {
990+
fn fill_canvas_pattern(&self, color: &Color) -> web_sys::CanvasPattern {
1002991
const PATTERN_WIDTH: usize = 4;
1003992
const PATTERN_HEIGHT: usize = 4;
1004993

@@ -1010,8 +999,6 @@ impl OverlayContext {
1010999
.expect("Failed to get canvas context")
10111000
.dyn_into()
10121001
.expect("Context should be a canvas 2d context");
1013-
// let [a, b, c, d, e, f] = DAffine2::from_scale(zoom).inverse().to_cols_array();
1014-
// pattern_context.set_transform(a, b, c, d, e, f);
10151002

10161003
// 4x4 pixels, 4 components (RGBA) per pixel
10171004
let mut data = [0_u8; 4 * PATTERN_WIDTH * PATTERN_HEIGHT];
@@ -1031,12 +1018,15 @@ impl OverlayContext {
10311018
pattern_context.put_image_data(&image_data, 0., 0.).unwrap();
10321019

10331020
let pattern = self.render_context.create_pattern_with_offscreen_canvas(&pattern_canvas, "repeat").unwrap().unwrap();
1034-
// let ctx_matrix = (|| {
1035-
// let dom_matrix = self.render_context.get_transform().unwrap();
1036-
// dom_matrix.
1037-
// SvgMatrix::from(dom_matrix.to_json())
1038-
// })();
1039-
// pattern.set_transform(&ctx_matrix);
1021+
let dom_matrix = self.render_context.get_transform().unwrap().inverse();
1022+
let set_pattern_transform = |pattern: &CanvasPattern, matrix: &DomMatrix| {
1023+
// Get the JS function: pattern.setTransform
1024+
let func = Reflect::get(pattern, &JsValue::from_str("setTransform"))?;
1025+
// Pass it the matrix
1026+
Reflect::apply(&func.into(), pattern, &Array::of1(matrix))?;
1027+
Ok::<(), JsValue>(())
1028+
};
1029+
set_pattern_transform(&pattern, &dom_matrix);
10401030

10411031
return pattern;
10421032
}
@@ -1067,12 +1057,12 @@ impl OverlayContext {
10671057
let element_transform = if has_real_stroke { layer_to_viewport * stroke.transform.inverse() } else { DAffine2::IDENTITY };
10681058

10691059
let [a, b, c, d, e, f] = element_transform.to_cols_array();
1070-
self.render_context.transform(a, b, c, d, e, f);
1060+
self.render_context.transform(a, b, c, d, e, f).expect("element_transform should be set to render stroke properly");
10711061

1072-
self.draw_path_from_subpaths(subpaths, applied_stroke_transform);
1062+
let is_closed = self.draw_path_from_subpaths(subpaths, applied_stroke_transform);
10731063

10741064
let do_fill = || {
1075-
self.render_context.set_fill_style_canvas_pattern(&self.fill_canvas_pattern(color, element_transform));
1065+
self.render_context.set_fill_style_canvas_pattern(&self.fill_canvas_pattern(color));
10761066
self.render_context.fill();
10771067
};
10781068
let do_stroke = |stroke_weight: f64| {
@@ -1085,28 +1075,42 @@ impl OverlayContext {
10851075
.set_global_composite_operation(composite_operation)
10861076
.expect("Failed to set global composite operation");
10871077
};
1088-
match (stroke.align, stroke.paint_order) {
1089-
(StrokeAlign::Inside, PaintOrder::StrokeAbove) => {
1090-
do_fill();
1091-
composite_mode("destination-out");
1092-
do_stroke(stroke.weight() * 2.);
1093-
}
1094-
(StrokeAlign::Inside, PaintOrder::StrokeBelow) => do_fill(),
1095-
(StrokeAlign::Center, PaintOrder::StrokeAbove) => {
1096-
do_fill();
1097-
composite_mode("destination-out");
1098-
do_stroke(stroke.weight());
1099-
}
1100-
(StrokeAlign::Center, PaintOrder::StrokeBelow) => {
1101-
do_fill();
1078+
1079+
if is_closed {
1080+
match (stroke.align, stroke.paint_order) {
1081+
(StrokeAlign::Inside, PaintOrder::StrokeAbove) => {
1082+
do_fill();
1083+
composite_mode("destination-out");
1084+
do_stroke(stroke.weight() * 2.);
1085+
}
1086+
(StrokeAlign::Inside, PaintOrder::StrokeBelow) => do_fill(),
1087+
(StrokeAlign::Center, PaintOrder::StrokeAbove) => {
1088+
do_fill();
1089+
composite_mode("destination-out");
1090+
do_stroke(stroke.weight());
1091+
}
1092+
(StrokeAlign::Center, PaintOrder::StrokeBelow) => {
1093+
do_fill();
1094+
}
1095+
// Paint order does not affect this
1096+
(StrokeAlign::Outside, _) => {
1097+
do_fill();
1098+
}
11021099
}
1103-
// Paint order does not affect this
1104-
(StrokeAlign::Outside, _) => {
1105-
do_fill();
1100+
} else {
1101+
match stroke.paint_order {
1102+
PaintOrder::StrokeAbove => {
1103+
do_fill();
1104+
composite_mode("destination-out");
1105+
do_stroke(stroke.weight());
1106+
}
1107+
PaintOrder::StrokeBelow => {
1108+
do_fill();
1109+
}
11061110
}
11071111
}
11081112
} else {
1109-
self.render_context.set_fill_style_canvas_pattern(&self.fill_canvas_pattern(color, DAffine2::IDENTITY));
1113+
self.render_context.set_fill_style_canvas_pattern(&self.fill_canvas_pattern(color));
11101114
self.render_context.fill();
11111115
}
11121116

@@ -1132,12 +1136,12 @@ impl OverlayContext {
11321136
let element_transform = if has_real_stroke { layer_to_viewport * stroke.transform.inverse() } else { DAffine2::IDENTITY };
11331137

11341138
let [a, b, c, d, e, f] = element_transform.to_cols_array();
1135-
self.render_context.transform(a, b, c, d, e, f);
1139+
self.render_context.transform(a, b, c, d, e, f).expect("element_transform should be set to render stroke properly");
11361140

1137-
self.draw_path_from_subpaths(subpaths, applied_stroke_transform);
1141+
let is_closed = self.draw_path_from_subpaths(subpaths, applied_stroke_transform);
11381142

11391143
let do_stroke = |stroke_weight: f64| {
1140-
self.render_context.set_stroke_style_canvas_pattern(&self.fill_canvas_pattern(color, element_transform));
1144+
self.render_context.set_stroke_style_canvas_pattern(&self.fill_canvas_pattern(color));
11411145
self.render_context.set_line_width(stroke_weight);
11421146
self.render_context.set_line_cap(stroke.cap.html_canvas_name().as_str());
11431147
self.render_context.set_line_join(stroke.join.html_canvas_name().as_str());
@@ -1153,27 +1157,40 @@ impl OverlayContext {
11531157
.set_global_composite_operation(composite_operation)
11541158
.expect("Failed to set global composite operation");
11551159
};
1156-
match (stroke.align, stroke.paint_order) {
1157-
(StrokeAlign::Inside, PaintOrder::StrokeAbove) => {
1158-
// TODO: Use something aside from destination-in
1159-
do_stroke(stroke.weight() * 2.);
1160-
composite_mode("destination-in");
1161-
do_fill();
1162-
}
1163-
(StrokeAlign::Inside, PaintOrder::StrokeBelow) => {}
1164-
(StrokeAlign::Center, PaintOrder::StrokeAbove) => {
1165-
do_stroke(stroke.weight());
1166-
}
1167-
(StrokeAlign::Center, PaintOrder::StrokeBelow) => {
1168-
do_stroke(stroke.weight());
1169-
composite_mode("destination-out");
1170-
do_fill();
1160+
1161+
if is_closed {
1162+
match (stroke.align, stroke.paint_order) {
1163+
(StrokeAlign::Inside, PaintOrder::StrokeAbove) => {
1164+
// Clips away the stroke lying outside the path drawn from the subpaths
1165+
self.render_context.clip();
1166+
do_stroke(stroke.weight() * 2.);
1167+
}
1168+
(StrokeAlign::Inside, PaintOrder::StrokeBelow) => {}
1169+
(StrokeAlign::Center, PaintOrder::StrokeAbove) => {
1170+
do_stroke(stroke.weight());
1171+
}
1172+
(StrokeAlign::Center, PaintOrder::StrokeBelow) => {
1173+
do_stroke(stroke.weight());
1174+
composite_mode("destination-out");
1175+
do_fill();
1176+
}
1177+
// Paint order does not affect this
1178+
(StrokeAlign::Outside, _) => {
1179+
do_stroke(stroke.weight() * 2.);
1180+
composite_mode("destination-out");
1181+
do_fill();
1182+
}
11711183
}
1172-
// Paint order does not affect this
1173-
(StrokeAlign::Outside, _) => {
1174-
do_stroke(stroke.weight() * 2.);
1175-
composite_mode("destination-out");
1176-
do_fill();
1184+
} else {
1185+
match stroke.paint_order {
1186+
PaintOrder::StrokeAbove => {
1187+
do_stroke(stroke.weight());
1188+
}
1189+
PaintOrder::StrokeBelow => {
1190+
do_stroke(stroke.weight());
1191+
composite_mode("destination-out");
1192+
do_fill();
1193+
}
11771194
}
11781195
}
11791196
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,9 @@ impl ToolTransition for FillTool {
7777
}
7878
}
7979

80-
pub fn close_to_subpath(mouse_pos: DVec2, subpath: Subpath<PointId>, stroke_width: f64, _zoom: f64, layer_to_viewport_transform: DAffine2) -> bool {
80+
pub fn close_to_subpath(mouse_pos: DVec2, subpath: Subpath<PointId>, stroke_width: f64, zoom: f64, layer_to_viewport_transform: DAffine2) -> bool {
8181
let mouse_pos = layer_to_viewport_transform.inverse().transform_point2(mouse_pos);
82+
// WARN: Zoom multiplied into stroke width gives false positive when zoom in 1000x
8283
let max_stroke_distance = stroke_width;
8384

8485
let subpath_bezpath = subpath.to_bezpath();

0 commit comments

Comments
 (0)