brush-paint: fix scaling of bbox-overlay PNGs + render skeleton as SVG vector
Two related fixes to the debug viewer's overlays: (1) The grid mask (was_ink padded by HULL_GRID_PAD=32) doesn't match the hull bbox the SVG places overlays at. encode_skeleton_b64, encode_grid_unpainted_b64 and encode_coverage_b64 were rendering grid-sized PNGs (W+64 × H+64) but the JSX placed them at hull-bbox coordinates (W × H), squashing them. Crop all three to the hull bbox by sampling from grid coords offset by HULL_GRID_PAD. Now they line up pixel-for-pixel with source_b64 / sdf_b64. Also promoted PAD to a module-level const HULL_GRID_PAD so the encoders and the grid-builder share one source of truth. (2) The skeleton was rendered as a rasterized PNG, which got blurry when zooming. Now traced as vector polylines: trace_skeleton_segments walks the skeleton graph from each special node (degree-1 endpoint or degree-≥3 junction) along its degree-2 chain neighbors, producing one polyline per skeleton edge. Closed loops (O / 0 cores) get their own segment with first==last point. PaintDebug now exposes: * skeleton_segments — Vec<polyline> for SVG rendering * skeleton_junctions — Vec<(x, y)> for green dots The rasterized skeleton_b64 is still produced for debugging the encoder; frontend uses the vector data instead. Stays sharp under any zoom. Bit-exact behavior preserved: alphabet report unchanged.
This commit is contained in:
@@ -645,16 +645,20 @@ export default function PaintDebugView({ passIdx = 0 }) {
|
|||||||
preserveAspectRatio="none" />
|
preserveAspectRatio="none" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{enabled.skeleton && debug.skeleton_b64 && (
|
{/* Vector skeleton — polylines per segment between special
|
||||||
<image
|
nodes. Stays sharp at any zoom. Junction dots in green. */}
|
||||||
href={debug.skeleton_b64} xlinkHref={debug.skeleton_b64}
|
{enabled.skeleton && (debug.skeleton_segments ?? []).map((seg, i) => (
|
||||||
x={debug.bounds[0]} y={debug.bounds[1]}
|
<polyline key={`sk${i}`}
|
||||||
width={debug.bounds[2] - debug.bounds[0] + 1}
|
points={seg.map(p => `${p[0]},${p[1]}`).join(' ')}
|
||||||
height={debug.bounds[3] - debug.bounds[1] + 1}
|
fill="none" stroke="#a3a3a3" strokeWidth={0.6}
|
||||||
opacity={0.95}
|
strokeLinecap="round" strokeLinejoin="round"
|
||||||
style={{ imageRendering: 'pixelated' }}
|
vectorEffect="non-scaling-stroke" />
|
||||||
preserveAspectRatio="none" />
|
))}
|
||||||
)}
|
{enabled.skeleton && (debug.skeleton_junctions ?? []).map((p, i) => (
|
||||||
|
<circle key={`sj${i}`} cx={p[0]} cy={p[1]} r={0.7}
|
||||||
|
fill="#22c55e" stroke="#000" strokeWidth={0.3}
|
||||||
|
vectorEffect="non-scaling-stroke" />
|
||||||
|
))}
|
||||||
|
|
||||||
{/* Per-endpoint init_dir arrows. Each arrow originates at the
|
{/* Per-endpoint init_dir arrows. Each arrow originates at the
|
||||||
endpoint and points along the skeleton tangent into the
|
endpoint and points along the skeleton tangent into the
|
||||||
|
|||||||
@@ -198,8 +198,16 @@ pub struct PaintDebug {
|
|||||||
pub walks: Vec<WalkTrace>,
|
pub walks: Vec<WalkTrace>,
|
||||||
/// Spur-pruned thinned skeleton, color-coded by per-pixel degree
|
/// Spur-pruned thinned skeleton, color-coded by per-pixel degree
|
||||||
/// (endpoint / junction / path). Same coord system as
|
/// (endpoint / junction / path). Same coord system as
|
||||||
/// `source_b64` / `sdf_b64` / `coverage_b64`.
|
/// `source_b64` / `sdf_b64` / `coverage_b64`. (Vector polylines
|
||||||
|
/// in `skeleton_segments` are usually preferable for display —
|
||||||
|
/// stay sharp under zoom.)
|
||||||
pub skeleton_b64: String,
|
pub skeleton_b64: String,
|
||||||
|
/// Skeleton as vector polylines, one per segment between special
|
||||||
|
/// nodes (endpoint or junction). Coordinates in hull-image space.
|
||||||
|
pub skeleton_segments: Vec<Vec<(f32, f32)>>,
|
||||||
|
/// Skeleton junction positions (degree ≥ 3). Endpoints are in
|
||||||
|
/// `endpoint_arrows`.
|
||||||
|
pub skeleton_junctions: Vec<(f32, f32)>,
|
||||||
/// Skeleton endpoints as render-ready arrows: each tuple is
|
/// Skeleton endpoints as render-ready arrows: each tuple is
|
||||||
/// `(x, y, dx, dy)` where `(x, y)` is the endpoint position in
|
/// `(x, y, dx, dy)` where `(x, y)` is the endpoint position in
|
||||||
/// hull coords and `(dx, dy)` is the unit init_dir along the
|
/// hull coords and `(dx, dy)` is the unit init_dir along the
|
||||||
@@ -454,19 +462,21 @@ fn encode_sdf_b64(hull: &Hull) -> (String, f32) {
|
|||||||
/// degree 1 → endpoint (red)
|
/// degree 1 → endpoint (red)
|
||||||
/// degree 2 → path (mid grey)
|
/// degree 2 → path (mid grey)
|
||||||
/// degree ≥ 3 → junction (green)
|
/// degree ≥ 3 → junction (green)
|
||||||
/// Empty pixels stay transparent. Sized to the hull's grid bbox so it
|
/// Empty pixels stay transparent. Cropped to the hull's bbox (not the
|
||||||
/// overlays directly on the source/sdf/coverage layers.
|
/// padded grid bbox) so the image lines up with `source_b64` /
|
||||||
|
/// `sdf_b64` / `coverage_b64` when overlaid in the viewer.
|
||||||
fn encode_skeleton_b64(hull_data: &HullData) -> String {
|
fn encode_skeleton_b64(hull_data: &HullData) -> String {
|
||||||
let bw = hull_data.width.max(1) as u32;
|
let bw = (hull_data.width - 2 * HULL_GRID_PAD).max(1);
|
||||||
let bh = hull_data.height.max(1) as u32;
|
let bh = (hull_data.height - 2 * HULL_GRID_PAD).max(1);
|
||||||
let mut img: image::RgbaImage = image::ImageBuffer::new(bw, bh);
|
let mut img: image::RgbaImage = image::ImageBuffer::new(bw as u32, bh as u32);
|
||||||
for ly in 0..hull_data.height {
|
for ly in 0..bh {
|
||||||
for lx in 0..hull_data.width {
|
for lx in 0..bw {
|
||||||
let idx = (ly * hull_data.width + lx) as usize;
|
let glx = lx + HULL_GRID_PAD; // grid-local
|
||||||
|
let gly = ly + HULL_GRID_PAD;
|
||||||
|
let idx = (gly * hull_data.width + glx) as usize;
|
||||||
if !hull_data.skeleton.get(idx) { continue; }
|
if !hull_data.skeleton.get(idx) { continue; }
|
||||||
// Count 8-connected in-skel neighbors.
|
let abs_x = (glx + hull_data.bx) as u32;
|
||||||
let abs_x = (lx + hull_data.bx) as u32;
|
let abs_y = (gly + hull_data.by) as u32;
|
||||||
let abs_y = (ly + hull_data.by) as u32;
|
|
||||||
let nbrs = zs_neighbors(abs_x, abs_y);
|
let nbrs = zs_neighbors(abs_x, abs_y);
|
||||||
let mut deg = 0;
|
let mut deg = 0;
|
||||||
for (nx, ny) in nbrs {
|
for (nx, ny) in nbrs {
|
||||||
@@ -478,9 +488,9 @@ fn encode_skeleton_b64(hull_data: &HullData) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let rgba = match deg {
|
let rgba = match deg {
|
||||||
0 | 1 => [244, 63, 94, 230], // endpoint — red
|
0 | 1 => [244, 63, 94, 230],
|
||||||
2 => [120, 120, 120, 200], // path — grey
|
2 => [120, 120, 120, 200],
|
||||||
_ => [ 34, 197, 94, 230], // junction — green
|
_ => [ 34, 197, 94, 230],
|
||||||
};
|
};
|
||||||
img.put_pixel(lx as u32, ly as u32, image::Rgba(rgba));
|
img.put_pixel(lx as u32, ly as u32, image::Rgba(rgba));
|
||||||
}
|
}
|
||||||
@@ -493,17 +503,17 @@ fn encode_skeleton_b64(hull_data: &HullData) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Snapshot the current `unpainted` BitMask as a transparent PNG —
|
/// Snapshot the current `unpainted` BitMask as a transparent PNG —
|
||||||
/// red pixels where ink is not yet painted, transparent elsewhere.
|
/// red pixels where ink is not yet painted. Cropped to hull bbox so
|
||||||
/// Same coord system as the other layers. Used by the per-stroke
|
/// it lines up with the other overlays.
|
||||||
/// scrubber so the viewer can see what was unpainted just before
|
|
||||||
/// stroke N began.
|
|
||||||
fn encode_grid_unpainted_b64(grid: &Grid) -> String {
|
fn encode_grid_unpainted_b64(grid: &Grid) -> String {
|
||||||
let bw = grid.width.max(1) as u32;
|
let bw = (grid.width - 2 * HULL_GRID_PAD).max(1);
|
||||||
let bh = grid.height.max(1) as u32;
|
let bh = (grid.height - 2 * HULL_GRID_PAD).max(1);
|
||||||
let mut img: image::RgbaImage = image::ImageBuffer::new(bw, bh);
|
let mut img: image::RgbaImage = image::ImageBuffer::new(bw as u32, bh as u32);
|
||||||
for ly in 0..grid.height {
|
for ly in 0..bh {
|
||||||
for lx in 0..grid.width {
|
for lx in 0..bw {
|
||||||
let idx = (ly * grid.width + lx) as usize;
|
let glx = lx + HULL_GRID_PAD;
|
||||||
|
let gly = ly + HULL_GRID_PAD;
|
||||||
|
let idx = (gly * grid.width + glx) as usize;
|
||||||
if grid.unpainted.get(idx) {
|
if grid.unpainted.get(idx) {
|
||||||
img.put_pixel(lx as u32, ly as u32, image::Rgba([244, 63, 94, 200]));
|
img.put_pixel(lx as u32, ly as u32, image::Rgba([244, 63, 94, 200]));
|
||||||
}
|
}
|
||||||
@@ -516,22 +526,16 @@ fn encode_grid_unpainted_b64(grid: &Grid) -> String {
|
|||||||
format!("data:image/png;base64,{}", b64)
|
format!("data:image/png;base64,{}", b64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Final unpainted ink, color-coded red. Cropped to hull bbox.
|
||||||
fn encode_coverage_b64(grid: &Grid) -> String {
|
fn encode_coverage_b64(grid: &Grid) -> String {
|
||||||
let bw = grid.width.max(1) as u32;
|
let bw = (grid.width - 2 * HULL_GRID_PAD).max(1);
|
||||||
let bh = grid.height.max(1) as u32;
|
let bh = (grid.height - 2 * HULL_GRID_PAD).max(1);
|
||||||
let mut img: image::RgbaImage = image::ImageBuffer::new(bw, bh);
|
let mut img: image::RgbaImage = image::ImageBuffer::new(bw as u32, bh as u32);
|
||||||
for ly in 0..grid.height {
|
for ly in 0..bh {
|
||||||
for lx in 0..grid.width {
|
for lx in 0..bw {
|
||||||
let idx = (ly * grid.width + lx) as usize;
|
let glx = lx + HULL_GRID_PAD;
|
||||||
// Was-ink test: we don't have a separate "was-ink" mask, but
|
let gly = ly + HULL_GRID_PAD;
|
||||||
// we can reconstruct from the grid's initial state (after
|
let idx = (gly * grid.width + glx) as usize;
|
||||||
// construction, unpainted == ink). After tracing, unpainted=true
|
|
||||||
// means "was ink and STILL not painted" — the gaps. Anything
|
|
||||||
// else is either background or already-painted-ink. We can't
|
|
||||||
// distinguish those without a second mask.
|
|
||||||
//
|
|
||||||
// For the coverage view, paint UNPAINTED ink red (= missed).
|
|
||||||
// Painted/background stays transparent.
|
|
||||||
if grid.unpainted.get(idx) {
|
if grid.unpainted.get(idx) {
|
||||||
img.put_pixel(lx as u32, ly as u32, image::Rgba([244, 63, 94, 200]));
|
img.put_pixel(lx as u32, ly as u32, image::Rgba([244, 63, 94, 200]));
|
||||||
}
|
}
|
||||||
@@ -617,6 +621,15 @@ struct HullData {
|
|||||||
/// vs path) is derived on demand by scanning 8-connected
|
/// vs path) is derived on demand by scanning 8-connected
|
||||||
/// neighbors of each skeleton pixel.
|
/// neighbors of each skeleton pixel.
|
||||||
skeleton: BitMask,
|
skeleton: BitMask,
|
||||||
|
/// Skeleton traced as polylines, one per segment between
|
||||||
|
/// "special" nodes (endpoints with degree 1 + junctions with
|
||||||
|
/// degree ≥ 3). Closed loops (O / 0 cores) are stored as
|
||||||
|
/// segments whose first and last point coincide. Coordinates
|
||||||
|
/// are absolute (hull-image space, same as `skel_endpoints`).
|
||||||
|
skeleton_segments: Vec<Vec<(f32, f32)>>,
|
||||||
|
/// Skeleton junction positions (degree ≥ 3 in the skeleton
|
||||||
|
/// graph). Endpoints are already in `skel_endpoints`.
|
||||||
|
skeleton_junctions: Vec<(f32, f32)>,
|
||||||
skeleton_length: u32,
|
skeleton_length: u32,
|
||||||
ink_total: i32,
|
ink_total: i32,
|
||||||
}
|
}
|
||||||
@@ -672,17 +685,20 @@ fn get_or_compute_hull_data(hull: &Hull) -> Arc<HullData> {
|
|||||||
cache.entry(key).or_insert_with(|| computed.clone()).clone()
|
cache.entry(key).or_insert_with(|| computed.clone()).clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pad the grid past the hull's AABB so that bg pixels swept by a brush
|
||||||
|
/// that overhangs the polygon (e.g. at the top of an `I`, or the
|
||||||
|
/// corners of a square letter) are counted instead of silently dropped
|
||||||
|
/// by the bounds check. Must exceed any brush_radius the optimizer
|
||||||
|
/// might try. The encoders crop back to hull bbox using this constant
|
||||||
|
/// so the rendered overlays line up with `source_b64` / `sdf_b64`
|
||||||
|
/// (which are hull-bbox-sized).
|
||||||
|
const HULL_GRID_PAD: i32 = 32;
|
||||||
|
|
||||||
fn compute_hull_data(hull: &Hull) -> HullData {
|
fn compute_hull_data(hull: &Hull) -> HullData {
|
||||||
// Pad the grid past the hull's AABB so that bg pixels swept by a
|
let bx = hull.bounds.x_min as i32 - HULL_GRID_PAD;
|
||||||
// brush that overhangs the polygon (e.g. at the top of an `I`,
|
let by = hull.bounds.y_min as i32 - HULL_GRID_PAD;
|
||||||
// or the corners of a square letter) are counted instead of
|
let width = (hull.bounds.x_max as i32 - hull.bounds.x_min as i32 + 1 + 2 * HULL_GRID_PAD).max(1);
|
||||||
// silently dropped by the bounds check. PAD must exceed any
|
let height = (hull.bounds.y_max as i32 - hull.bounds.y_min as i32 + 1 + 2 * HULL_GRID_PAD).max(1);
|
||||||
// brush_radius the optimizer might try.
|
|
||||||
const PAD: i32 = 32;
|
|
||||||
let bx = hull.bounds.x_min as i32 - PAD;
|
|
||||||
let by = hull.bounds.y_min as i32 - PAD;
|
|
||||||
let width = (hull.bounds.x_max as i32 - hull.bounds.x_min as i32 + 1 + 2 * PAD).max(1);
|
|
||||||
let height = (hull.bounds.y_max as i32 - hull.bounds.y_min as i32 + 1 + 2 * PAD).max(1);
|
|
||||||
let cells = (width * height) as usize;
|
let cells = (width * height) as usize;
|
||||||
let mut was_ink = BitMask::new(cells);
|
let mut was_ink = BitMask::new(cells);
|
||||||
let mut sdf = vec![0.0_f32; cells];
|
let mut sdf = vec![0.0_f32; cells];
|
||||||
@@ -732,12 +748,111 @@ fn compute_hull_data(hull: &Hull) -> HullData {
|
|||||||
if lx < 0 || ly < 0 || lx >= width || ly >= height { continue; }
|
if lx < 0 || ly < 0 || lx >= width || ly >= height { continue; }
|
||||||
skeleton.set((ly * width + lx) as usize);
|
skeleton.set((ly * width + lx) as usize);
|
||||||
}
|
}
|
||||||
|
let (skeleton_segments, skeleton_junctions) = trace_skeleton_segments(&skel);
|
||||||
let skeleton_length = skel.len() as u32;
|
let skeleton_length = skel.len() as u32;
|
||||||
HullData { bx, by, width, height, was_ink, sdf, sdf_values_sorted,
|
HullData { bx, by, width, height, was_ink, sdf, sdf_values_sorted,
|
||||||
skel_endpoints, skel_endpoints_init_dir, skeleton,
|
skel_endpoints, skel_endpoints_init_dir, skeleton,
|
||||||
|
skeleton_segments, skeleton_junctions,
|
||||||
skeleton_length, ink_total: count }
|
skeleton_length, ink_total: count }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decompose the skeleton into polyline segments connecting "special"
|
||||||
|
/// nodes (degree-1 endpoints and degree-≥3 junctions), plus closed
|
||||||
|
/// loops for components that have no special nodes (O / 0 / D-style
|
||||||
|
/// closed shapes). Walking from each special node along its degree-2
|
||||||
|
/// chain neighbors produces one segment per skeleton edge in the
|
||||||
|
/// implicit graph; the visited-edge set prevents duplicates.
|
||||||
|
fn trace_skeleton_segments(skel: &HashSet<(u32, u32)>)
|
||||||
|
-> (Vec<Vec<(f32, f32)>>, Vec<(f32, f32)>)
|
||||||
|
{
|
||||||
|
let neighbors_of = |p: (u32, u32)| -> Vec<(u32, u32)> {
|
||||||
|
zs_neighbors(p.0, p.1).into_iter()
|
||||||
|
.filter(|n| skel.contains(n))
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
let normalize = |a: (u32, u32), b: (u32, u32)| -> ((u32, u32), (u32, u32)) {
|
||||||
|
if a <= b { (a, b) } else { (b, a) }
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut special: HashSet<(u32, u32)> = HashSet::new();
|
||||||
|
let mut junctions: Vec<(f32, f32)> = Vec::new();
|
||||||
|
for &p in skel {
|
||||||
|
let deg = neighbors_of(p).len();
|
||||||
|
if deg == 1 || deg >= 3 { special.insert(p); }
|
||||||
|
if deg >= 3 { junctions.push((p.0 as f32, p.1 as f32)); }
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut segments: Vec<Vec<(f32, f32)>> = Vec::new();
|
||||||
|
let mut visited_edges: HashSet<((u32, u32), (u32, u32))> = HashSet::new();
|
||||||
|
|
||||||
|
// Walk one segment per (special-node, neighbor) edge.
|
||||||
|
for &s in &special {
|
||||||
|
for n in neighbors_of(s) {
|
||||||
|
let edge = normalize(s, n);
|
||||||
|
if visited_edges.contains(&edge) { continue; }
|
||||||
|
visited_edges.insert(edge);
|
||||||
|
let mut seg: Vec<(f32, f32)> = vec![
|
||||||
|
(s.0 as f32, s.1 as f32),
|
||||||
|
(n.0 as f32, n.1 as f32),
|
||||||
|
];
|
||||||
|
let mut prev = s;
|
||||||
|
let mut cur = n;
|
||||||
|
while !special.contains(&cur) {
|
||||||
|
let nbrs = neighbors_of(cur);
|
||||||
|
let next_opt = nbrs.iter().copied().find(|&p| p != prev);
|
||||||
|
let Some(next) = next_opt else { break };
|
||||||
|
let next_edge = normalize(cur, next);
|
||||||
|
if visited_edges.contains(&next_edge) { break; }
|
||||||
|
visited_edges.insert(next_edge);
|
||||||
|
seg.push((next.0 as f32, next.1 as f32));
|
||||||
|
prev = cur;
|
||||||
|
cur = next;
|
||||||
|
}
|
||||||
|
segments.push(seg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Isolated cycles: connected components with NO special nodes
|
||||||
|
// (e.g. O's skeleton). Pick any unvisited pixel, walk until we
|
||||||
|
// either return to start or run out of unvisited neighbors.
|
||||||
|
let mut visited_pixels: HashSet<(u32, u32)> = HashSet::new();
|
||||||
|
for seg in &segments {
|
||||||
|
for &(x, y) in seg {
|
||||||
|
visited_pixels.insert((x as u32, y as u32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for &start in skel {
|
||||||
|
if visited_pixels.contains(&start) || special.contains(&start) { continue; }
|
||||||
|
let mut seg: Vec<(f32, f32)> = vec![(start.0 as f32, start.1 as f32)];
|
||||||
|
visited_pixels.insert(start);
|
||||||
|
let mut prev: Option<(u32, u32)> = None;
|
||||||
|
let mut cur = start;
|
||||||
|
loop {
|
||||||
|
let nbrs = neighbors_of(cur);
|
||||||
|
let next = nbrs.iter().copied()
|
||||||
|
.find(|&p| Some(p) != prev && !visited_pixels.contains(&p));
|
||||||
|
match next {
|
||||||
|
Some(n) => {
|
||||||
|
visited_pixels.insert(n);
|
||||||
|
seg.push((n.0 as f32, n.1 as f32));
|
||||||
|
prev = Some(cur);
|
||||||
|
cur = n;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Close the loop if we're adjacent to start.
|
||||||
|
if nbrs.iter().any(|&p| p == start) {
|
||||||
|
seg.push((start.0 as f32, start.1 as f32));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if seg.len() >= 2 { segments.push(seg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
(segments, junctions)
|
||||||
|
}
|
||||||
|
|
||||||
// ── Coverage grid: per-call mutable state, sized to the hull's bbox ─────
|
// ── Coverage grid: per-call mutable state, sized to the hull's bbox ─────
|
||||||
|
|
||||||
struct Grid {
|
struct Grid {
|
||||||
@@ -1821,6 +1936,8 @@ fn paint_fill_debug_inner(hull: &Hull, params: &PaintParams,
|
|||||||
.zip(grid.hull.skel_endpoints_init_dir.iter())
|
.zip(grid.hull.skel_endpoints_init_dir.iter())
|
||||||
.map(|(&(ex, ey), &(dx, dy))| (ex as f32, ey as f32, dx, dy))
|
.map(|(&(ex, ey), &(dx, dy))| (ex as f32, ey as f32, dx, dy))
|
||||||
.collect();
|
.collect();
|
||||||
|
let skeleton_segments = grid.hull.skeleton_segments.clone();
|
||||||
|
let skeleton_junctions = grid.hull.skeleton_junctions.clone();
|
||||||
let disk_offsets = grid.disk_offsets.clone();
|
let disk_offsets = grid.disk_offsets.clone();
|
||||||
PaintDebug {
|
PaintDebug {
|
||||||
bounds,
|
bounds,
|
||||||
@@ -1841,6 +1958,8 @@ fn paint_fill_debug_inner(hull: &Hull, params: &PaintParams,
|
|||||||
start_points: starts,
|
start_points: starts,
|
||||||
walks,
|
walks,
|
||||||
skeleton_b64,
|
skeleton_b64,
|
||||||
|
skeleton_segments,
|
||||||
|
skeleton_junctions,
|
||||||
endpoint_arrows,
|
endpoint_arrows,
|
||||||
disk_offsets,
|
disk_offsets,
|
||||||
stroke_seedings,
|
stroke_seedings,
|
||||||
|
|||||||
Reference in New Issue
Block a user