Merge PR #1829 (updated head)

This commit is contained in:
Brandon Thomas
2026-10-15 03:01:12 -04:00
2 changed files with 42 additions and 1 deletions
+13 -1
View File
@@ -160,6 +160,9 @@ const MAX_TAPER_STEPS: usize = 64;
fn connected_strokes(from: Point, last: &BrushStroke, to: BrushStroke) -> Vec<BrushStroke> {
let Some(&first) = to.points.first() else { return vec![to] };
let same = |a: f64, b: f64| (a - b).abs() <= 1e-9;
// pen pressure along the line: the last dab's at its start, the click's at its end (none when neither has any)
let pressured = !last.pressure.is_empty() || !to.pressure.is_empty();
let (p_from, p_to) = (last.pressure_at(0), to.pressure_at(0));
if same(last.size, to.size) && same(last.feather, to.feather) && same(last.flow, to.flow) {
let mut points = Vec::with_capacity(to.points.len() + 1);
points.push(from);
@@ -181,6 +184,7 @@ fn connected_strokes(from: Point, last: &BrushStroke, to: BrushStroke) -> Vec<Br
let mut out: Vec<BrushStroke> = (0..steps)
.map(|i| {
let (t0, t1) = (i as f64 / steps as f64, (i + 1) as f64 / steps as f64);
let p = |t: f64| (f64::from(p_from) + (f64::from(p_to) - f64::from(p_from)) * t) as f32;
BrushStroke {
points: vec![at(t0), at(t1)],
size: lerp(last.size, to.size, t1),
@@ -325,7 +329,15 @@ pub fn specs() -> Vec<CommandSpec> {
let anchor = strokes.last().filter(|_| connect).and_then(|l| {
Some((
*l.points.last()?,
BrushStroke { points: Vec::new(), size: l.size, feather: l.feather, flow: l.flow, ..BrushStroke::default() },
BrushStroke {
points: Vec::new(),
size: l.size,
feather: l.feather,
flow: l.flow,
// the pressure the last dab was painted with, when the stroke recorded any
pressure: if l.pressure.is_empty() { Vec::new() } else { vec![l.pressure_at(l.points.len().saturating_sub(1))] },
..BrushStroke::default()
},
))
});
match anchor {
+29
View File
@@ -368,6 +368,35 @@ fn brush_strokes_keep_pen_pressure_and_refuse_bad_lists() {
assert!(!json.contains("pressure"), "and stores none: {json}");
}
/// A ⇧-click line carries pen pressure: the last dab's at its start and the click's at its end, stepping along a
/// tapered line; with no pressure on either side there is none.
#[test]
fn connected_brush_strokes_carry_pen_pressure() {
use lightcraft_develop::MaskShape;
let mut s = demo();
let id = s.active().unwrap();
let strokes = |s: &Session| {
let d = s.develop_of(id).unwrap();
let MaskShape::Brush { strokes } = &d.masks[0].components[0].shape else { panic!("brush") };
strokes.clone()
};
s.execute("mask.brushStroke", &json!({"points": [[0.2, 0.5]], "size": 0.02, "pressure": [0.4]})).unwrap();
s.execute("mask.brushStroke", &json!({"points": [[0.6, 0.5]], "size": 0.02, "pressure": [1.0], "connect": true})).unwrap();
assert_eq!(strokes(&s)[1].pressure, vec![0.4, 1.0]);
s.execute("mask.brushStroke", &json!({"points": [[0.6, 0.9]], "size": 0.04, "pressure": [0.2], "connect": true})).unwrap();
let taper = &strokes(&s)[2..];
assert!(taper.iter().all(|t| t.pressure.len() == 2));
assert!((taper[0].pressure[0] - 1.0).abs() < 1e-6 && (taper.last().unwrap().pressure[1] - 0.2).abs() < 1e-6);
assert!(taper.windows(2).all(|w| w[1].pressure[0] <= w[0].pressure[0]));
// a mouse: no pressure anywhere
let mut m = demo();
m.execute("mask.brushStroke", &json!({"points": [[0.2, 0.5]], "size": 0.02})).unwrap();
m.execute("mask.brushStroke", &json!({"points": [[0.6, 0.5]], "size": 0.02, "connect": true})).unwrap();
let d = m.develop_of(m.active().unwrap()).unwrap();
let MaskShape::Brush { strokes } = &d.masks[0].components[0].shape else { panic!("brush") };
assert!(strokes[1].pressure.is_empty());
}
#[test]
fn masks_crop_and_render() {
let mut s = demo();