From 56df5969f586a374fb548336e53a682220f5645b Mon Sep 17 00:00:00 2001 From: andeecollard Date: Thu, 15 Oct 2026 18:06:48 +0000 Subject: [PATCH] Image Trace: keep shared corners identical at nearby junctions When a long horizontal or vertical run crosses a three-colour junction one or two pixels before a corner, the corner correction for that run borrowed a neighbouring crack's shift that the region on the other side of the junction never applied, leaving hairline gaps between abutting colours. The correction now applies only when all three cracks it uses lie in the same shared piece. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_018tBcdwNhptqtmX32JCGFzB --- crates/trace/src/subpixel.rs | 72 +++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/crates/trace/src/subpixel.rs b/crates/trace/src/subpixel.rs index 8420a2966..534fc1060 100644 --- a/crates/trace/src/subpixel.rs +++ b/crates/trace/src/subpixel.rs @@ -185,10 +185,13 @@ pub(crate) fn outline( continue; } let (before, edge) = (&edges[b], &edges[e]); - if before.len >= 3 { + // The three cracks must belong to the same shared piece. A junction + // can split a long raw edge near this corner; the opposite region + // then sees a shorter edge and would not make this correction. + if before.len >= 3 && !(1..=2).any(|k| junction(before.first + before.len - k)) { shifts[before.first + before.len - 1] = shifts[before.first + before.len - 2]; } - if edge.len >= 3 { + if edge.len >= 3 && !(1..=2).any(|k| junction(edge.first + k)) { shifts[edge.first] = shifts[edge.first + 1]; } } @@ -362,6 +365,71 @@ mod tests { assert!(pts.iter().all(|p| p.x.is_finite() && p.y.is_finite())); } + fn check_junction_near_corner(distance: usize) { + // The bottom region's long top run crosses a junction near its corner. + // Its neighbour sees only the short shared part after the junction. + let (w, h) = (12, 12); + for turns in 0..4 { + for palette in [[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]] { + let mut labels = vec![0u32; w * h]; + for y in 0..h { + for x in 0..w { + let label = if y < 4 && x < 5 - distance { + 0 + } else if y >= 4 && x < 5 { + 2 + } else { + 1 + }; + let (mut xx, mut yy) = (x, y); + for _ in 0..turns { + (xx, yy) = (h - 1 - yy, xx); + } + labels[yy * w + xx] = palette[label]; + } + } + let across = |c: Crack| { + let (a, b) = (c.at(0).unwrap(), c.at(1).unwrap()); + let wave = 0.4 * ((a + b) as f64).sin(); + (if labels[a] < labels[b] { wave } else { -wave }, labels[b]) + }; + let pieces = |label| { + let mask: Vec = labels.iter().map(|&l| l == label).collect(); + outline(&loop_of(&mask, w, h), w, h, &across, &grid) + }; + let rotate = |mut p: Point| { + for _ in 0..turns { + p = Point::new(h as f64 - p.y, p.x); + } + p + }; + let (start, end) = (rotate(Point::new((5 - distance) as f64, 4.0)), rotate(Point::new(5.0, 12.0))); + let edge = |pieces: &[Piece]| { + pieces + .iter() + .find(|p| { + p.pts.first() == Some(&start) && p.pts.last() == Some(&end) || p.pts.last() == Some(&start) && p.pts.first() == Some(&end) + }) + .unwrap() + .clone() + }; + let (mut ours, theirs) = (edge(&pieces(palette[1])), edge(&pieces(palette[2]))); + ours.pts.reverse(); + assert_eq!(ours, theirs, "distance={distance}, turns={turns}, palette={palette:?}"); + } + } + } + + #[test] + fn junction_one_pixel_before_corner_keeps_shared_edge_equal() { + check_junction_near_corner(1); + } + + #[test] + fn junction_two_pixels_before_corner_keeps_shared_edge_equal() { + check_junction_near_corner(2); + } + #[test] fn two_regions_get_the_same_points_for_their_shared_edge() { // Left and right halves meet along a staircase; each sees the other's shifts negated.