|
|
|
|
@@ -102,21 +102,6 @@ pub struct PaintParams {
|
|
|
|
|
/// admits sharper turns (down to direct reversals); closer to 0
|
|
|
|
|
/// rejects mild backward components.
|
|
|
|
|
pub back_dir_cutoff: f32,
|
|
|
|
|
/// Number of relax↔shorten tick-tock rounds after the bidirectional
|
|
|
|
|
/// walk. Each round runs (a) waypoint relaxation toward unpainted ink,
|
|
|
|
|
/// then (b) waypoint pruning where it doesn't lose coverage. 0 disables.
|
|
|
|
|
pub polish_iters: u32,
|
|
|
|
|
/// How far (in brush radii) to search for unpainted ink near each
|
|
|
|
|
/// waypoint during relaxation.
|
|
|
|
|
pub polish_search_factor: f32,
|
|
|
|
|
/// Per-pixel penalty when the brush hangs over background (off-glyph
|
|
|
|
|
/// disk overlap). 1.0 = "1 bg pixel under brush is worth 1 ink pixel
|
|
|
|
|
/// of coverage." Used by the polish/relax pass to bias waypoints
|
|
|
|
|
/// onto the centerline. The walker proper enforces ink containment
|
|
|
|
|
/// as a hard constraint (waypoint center must be on ink) and does
|
|
|
|
|
/// not include this term — bg under the disk is unavoidable when
|
|
|
|
|
/// the brush is wider than the stroke.
|
|
|
|
|
pub bg_penalty: f32,
|
|
|
|
|
/// Minimum unpainted-ink component size (as a multiplier of brush
|
|
|
|
|
/// area = π·r²) to be eligible as a stroke seed. Sub-threshold
|
|
|
|
|
/// components stay in the unpainted mask and count against
|
|
|
|
|
@@ -149,9 +134,6 @@ impl Default for PaintParams {
|
|
|
|
|
walk_bg_penalty: 0.69,
|
|
|
|
|
min_score_factor: 0.20,
|
|
|
|
|
back_dir_cutoff: -0.7,
|
|
|
|
|
polish_iters: 2,
|
|
|
|
|
polish_search_factor: 0.5,
|
|
|
|
|
bg_penalty: 2.0,
|
|
|
|
|
min_component_factor: 1.49,
|
|
|
|
|
max_steps_per_stroke: 4000,
|
|
|
|
|
max_strokes: 12,
|
|
|
|
|
@@ -997,12 +979,6 @@ fn trace_stroke(start: (f32, f32), grid: &mut Grid,
|
|
|
|
|
walk_log: Option<&mut Vec<WalkTrace>>,
|
|
|
|
|
stroke_idx: u32) -> Vec<(f32, f32)>
|
|
|
|
|
{
|
|
|
|
|
// Snapshot pre-stroke ink state so we can relax against the original
|
|
|
|
|
// unpainted mask (without our own path's contributions confusing the
|
|
|
|
|
// "is this pixel uncovered?" question).
|
|
|
|
|
let pre_unpainted = grid.unpainted.clone();
|
|
|
|
|
let pre_ink_remaining = grid.ink_remaining;
|
|
|
|
|
|
|
|
|
|
let step_size = params.step_size_factor * brush_radius;
|
|
|
|
|
let brush_area = std::f32::consts::PI * brush_radius * brush_radius;
|
|
|
|
|
let min_score = params.min_score_factor * brush_area;
|
|
|
|
|
@@ -1022,13 +998,12 @@ fn trace_stroke(start: (f32, f32), grid: &mut Grid,
|
|
|
|
|
}
|
|
|
|
|
if forward.len() < 2 { return forward; }
|
|
|
|
|
|
|
|
|
|
let combined = {
|
|
|
|
|
let dx = forward[1].0 - forward[0].0;
|
|
|
|
|
let dy = forward[1].1 - forward[0].1;
|
|
|
|
|
let mag = (dx * dx + dy * dy).sqrt();
|
|
|
|
|
if mag < 1e-6 {
|
|
|
|
|
forward
|
|
|
|
|
} else {
|
|
|
|
|
return forward;
|
|
|
|
|
}
|
|
|
|
|
let back_init = (-dx / mag, -dy / mag);
|
|
|
|
|
let mut backward_trace = walk_log.as_ref().map(|_| WalkTrace {
|
|
|
|
|
kind: "backward".into(), stroke_idx, start,
|
|
|
|
|
@@ -1041,252 +1016,12 @@ fn trace_stroke(start: (f32, f32), grid: &mut Grid,
|
|
|
|
|
log.push(t);
|
|
|
|
|
}
|
|
|
|
|
if backward.len() < 2 {
|
|
|
|
|
forward
|
|
|
|
|
} else {
|
|
|
|
|
let mut c: Vec<(f32, f32)> = Vec::with_capacity(forward.len() + backward.len());
|
|
|
|
|
for &p in backward.iter().rev() { c.push(p); }
|
|
|
|
|
for &p in forward.iter().skip(1) { c.push(p); }
|
|
|
|
|
c
|
|
|
|
|
return forward;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ── Relaxation ──────────────────────────────────────────────────────
|
|
|
|
|
if params.polish_iters == 0 || combined.len() < 3 {
|
|
|
|
|
return combined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Restore the pre-stroke unpainted mask so the relaxation sees the
|
|
|
|
|
// FULL set of pixels this stroke could potentially cover, not what's
|
|
|
|
|
// left over after the walk's painting.
|
|
|
|
|
grid.unpainted = pre_unpainted;
|
|
|
|
|
grid.ink_remaining = pre_ink_remaining;
|
|
|
|
|
|
|
|
|
|
let polished = polish_path(combined, grid, brush_radius, params);
|
|
|
|
|
|
|
|
|
|
// Now paint the final polished path back into the grid.
|
|
|
|
|
for &p in &polished { grid.paint_disk(p, brush_radius); }
|
|
|
|
|
|
|
|
|
|
polished
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Tick-tock relax + shorten. Each round:
|
|
|
|
|
/// 1. Relax: nudge each interior waypoint toward unpainted ink (subject
|
|
|
|
|
/// to the "stay-on-shape" constraint and outside-penalty).
|
|
|
|
|
/// 2. Shorten: drop waypoints whose removal causes zero coverage loss.
|
|
|
|
|
fn polish_path(mut path: Vec<(f32, f32)>, grid: &Grid,
|
|
|
|
|
brush_radius: f32, params: &PaintParams) -> Vec<(f32, f32)>
|
|
|
|
|
{
|
|
|
|
|
if path.len() < 3 { return path; }
|
|
|
|
|
|
|
|
|
|
// Coverage count: how many waypoints' brushes cover each pixel. We
|
|
|
|
|
// maintain this incrementally across both relax and shorten passes.
|
|
|
|
|
let mut count: Vec<u16> = vec![0; grid.unpainted.len()];
|
|
|
|
|
for &p in &path { stamp_count(&mut count, grid, p, brush_radius, 1); }
|
|
|
|
|
|
|
|
|
|
for _ in 0..params.polish_iters {
|
|
|
|
|
let any_relaxed = relax_step(&mut path, &mut count, grid, brush_radius, params);
|
|
|
|
|
let any_shortened = shorten_step(&mut path, &mut count, grid, brush_radius);
|
|
|
|
|
if !any_relaxed && !any_shortened { break; }
|
|
|
|
|
}
|
|
|
|
|
path
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// One sweep of waypoint relaxation. Returns true if any waypoint moved.
|
|
|
|
|
/// Only accepts perturbations that:
|
|
|
|
|
/// - Land the waypoint INSIDE the original glyph (no off-shape drift)
|
|
|
|
|
/// - Improve net score (ink-gain - ink-loss - bg_penalty * background-gain)
|
|
|
|
|
fn relax_step(path: &mut Vec<(f32, f32)>, count: &mut Vec<u16>,
|
|
|
|
|
grid: &Grid, brush_radius: f32, params: &PaintParams) -> bool
|
|
|
|
|
{
|
|
|
|
|
let n = path.len();
|
|
|
|
|
if n < 3 { return false; }
|
|
|
|
|
let max_perturb = brush_radius * 0.6;
|
|
|
|
|
let search_r = brush_radius * params.polish_search_factor;
|
|
|
|
|
let mut moved = false;
|
|
|
|
|
|
|
|
|
|
// Iterate ALL waypoints including endpoints. A true dead-end has no
|
|
|
|
|
// unpainted ink nearby, so `nearest_uncovered_ink` returns None and
|
|
|
|
|
// the loop body bails — endpoints stay put. A misplaced edge-hugger
|
|
|
|
|
// gets pulled toward the centerline like any interior waypoint.
|
|
|
|
|
for i in 0..n {
|
|
|
|
|
let p_old = path[i];
|
|
|
|
|
let target = match nearest_uncovered_ink(p_old, search_r, grid, count) {
|
|
|
|
|
Some(t) => t, None => continue,
|
|
|
|
|
};
|
|
|
|
|
let dx = target.0 - p_old.0;
|
|
|
|
|
let dy = target.1 - p_old.1;
|
|
|
|
|
let dist = (dx * dx + dy * dy).sqrt();
|
|
|
|
|
if dist < 0.3 { continue; }
|
|
|
|
|
let shift = (dist * 0.7).min(max_perturb);
|
|
|
|
|
let p_new = (p_old.0 + dx / dist * shift,
|
|
|
|
|
p_old.1 + dy / dist * shift);
|
|
|
|
|
|
|
|
|
|
// Hard constraint: waypoint center must be inside the original
|
|
|
|
|
// glyph. Otherwise the gcode's pen would draw a line outside the
|
|
|
|
|
// letter — visible, ugly, fatal.
|
|
|
|
|
if !grid.is_ink(p_new.0.round() as i32, p_new.1.round() as i32) { continue; }
|
|
|
|
|
|
|
|
|
|
let score = evaluate_perturbation(p_old, p_new, brush_radius, grid, count, params);
|
|
|
|
|
if score > 0.0 {
|
|
|
|
|
stamp_count(count, grid, p_old, brush_radius, -1);
|
|
|
|
|
stamp_count(count, grid, p_new, brush_radius, 1);
|
|
|
|
|
path[i] = p_new;
|
|
|
|
|
moved = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
moved
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// One sweep of waypoint pruning. Removes any interior waypoint whose
|
|
|
|
|
/// brush is FULLY redundant (every ink pixel under it is covered by some
|
|
|
|
|
/// other waypoint too). Returns true if any waypoint was removed.
|
|
|
|
|
fn shorten_step(path: &mut Vec<(f32, f32)>, count: &mut Vec<u16>,
|
|
|
|
|
grid: &Grid, brush_radius: f32) -> bool
|
|
|
|
|
{
|
|
|
|
|
if path.len() < 3 { return false; }
|
|
|
|
|
let mut removed_any = false;
|
|
|
|
|
let mut i = 1usize;
|
|
|
|
|
while i + 1 < path.len() {
|
|
|
|
|
let p = path[i];
|
|
|
|
|
if waypoint_is_redundant(p, brush_radius, grid, count) {
|
|
|
|
|
stamp_count(count, grid, p, brush_radius, -1);
|
|
|
|
|
path.remove(i);
|
|
|
|
|
removed_any = true;
|
|
|
|
|
// Don't increment i — the next waypoint shifted into i.
|
|
|
|
|
} else {
|
|
|
|
|
i += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
removed_any
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// True iff every pre-stroke-unpainted ink pixel under disk(p, r) is
|
|
|
|
|
/// covered by at least one OTHER waypoint (i.e., count > 1 there). When
|
|
|
|
|
/// true, removing waypoint at p doesn't drop coverage anywhere.
|
|
|
|
|
fn waypoint_is_redundant(p: (f32, f32), brush_radius: f32,
|
|
|
|
|
grid: &Grid, count: &[u16]) -> bool
|
|
|
|
|
{
|
|
|
|
|
let cx_i = p.0.round() as i32;
|
|
|
|
|
let cy_i = p.1.round() as i32;
|
|
|
|
|
let r = (brush_radius + 1.0).ceil() as i32;
|
|
|
|
|
let r2 = brush_radius * brush_radius;
|
|
|
|
|
for dy in -r..=r {
|
|
|
|
|
for dx in -r..=r {
|
|
|
|
|
let ddx = (cx_i + dx) as f32 - p.0;
|
|
|
|
|
let ddy = (cy_i + dy) as f32 - p.1;
|
|
|
|
|
if ddx * ddx + ddy * ddy > r2 { continue; }
|
|
|
|
|
let lx = cx_i + dx - grid.bx;
|
|
|
|
|
let ly = cy_i + dy - grid.by;
|
|
|
|
|
if lx < 0 || ly < 0 || lx >= grid.width || ly >= grid.height { continue; }
|
|
|
|
|
let idx = (ly * grid.width + lx) as usize;
|
|
|
|
|
if !grid.unpainted[idx] { continue; } // ineligible pixel
|
|
|
|
|
if count[idx] <= 1 { return false; } // we'd lose this one
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Add `delta` to coverage count over the disk(p, radius). Used to
|
|
|
|
|
/// install or remove a waypoint's brush contribution.
|
|
|
|
|
fn stamp_count(count: &mut [u16], grid: &Grid, p: (f32, f32), radius: f32, delta: i16) {
|
|
|
|
|
let cx_i = p.0.round() as i32;
|
|
|
|
|
let cy_i = p.1.round() as i32;
|
|
|
|
|
let r = (radius + 1.0).ceil() as i32;
|
|
|
|
|
let r2 = radius * radius;
|
|
|
|
|
for dy in -r..=r {
|
|
|
|
|
for dx in -r..=r {
|
|
|
|
|
let ddx = (cx_i + dx) as f32 - p.0;
|
|
|
|
|
let ddy = (cy_i + dy) as f32 - p.1;
|
|
|
|
|
if ddx * ddx + ddy * ddy > r2 { continue; }
|
|
|
|
|
let lx = cx_i + dx - grid.bx;
|
|
|
|
|
let ly = cy_i + dy - grid.by;
|
|
|
|
|
if lx < 0 || ly < 0 || lx >= grid.width || ly >= grid.height { continue; }
|
|
|
|
|
let idx = (ly * grid.width + lx) as usize;
|
|
|
|
|
count[idx] = (count[idx] as i32 + delta as i32).max(0) as u16;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Find the nearest pre-stroke-unpainted ink pixel to `from`, within
|
|
|
|
|
/// `search_radius`, that isn't already covered by some other waypoint's
|
|
|
|
|
/// brush (count == 0).
|
|
|
|
|
fn nearest_uncovered_ink(from: (f32, f32), search_radius: f32,
|
|
|
|
|
grid: &Grid, count: &[u16]) -> Option<(f32, f32)>
|
|
|
|
|
{
|
|
|
|
|
let r = search_radius.ceil() as i32;
|
|
|
|
|
let r2 = search_radius * search_radius;
|
|
|
|
|
let mut best: Option<((f32, f32), f32)> = None;
|
|
|
|
|
for dy in -r..=r {
|
|
|
|
|
for dx in -r..=r {
|
|
|
|
|
let d2 = (dx * dx + dy * dy) as f32;
|
|
|
|
|
if d2 > r2 { continue; }
|
|
|
|
|
let px = from.0 as i32 + dx;
|
|
|
|
|
let py = from.1 as i32 + dy;
|
|
|
|
|
let lx = px - grid.bx;
|
|
|
|
|
let ly = py - grid.by;
|
|
|
|
|
if lx < 0 || ly < 0 || lx >= grid.width || ly >= grid.height { continue; }
|
|
|
|
|
let idx = (ly * grid.width + lx) as usize;
|
|
|
|
|
if grid.unpainted[idx] && count[idx] == 0 {
|
|
|
|
|
match best {
|
|
|
|
|
None => best = Some(((px as f32, py as f32), d2)),
|
|
|
|
|
Some((_, bd2)) if d2 < bd2 => best = Some(((px as f32, py as f32), d2)),
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
best.map(|(p, _)| p)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Score for moving waypoint p_old → p_new. Three terms:
|
|
|
|
|
/// + gain — pre-stroke-unpainted ink that newly becomes covered
|
|
|
|
|
/// - loss — uniquely-covered ink that would become uncovered
|
|
|
|
|
/// - background — extra background-pixel coverage by the new brush
|
|
|
|
|
/// position (waste; weighted by `bg_penalty`)
|
|
|
|
|
/// Net > 0 → keep the move.
|
|
|
|
|
fn evaluate_perturbation(p_old: (f32, f32), p_new: (f32, f32), brush_radius: f32,
|
|
|
|
|
grid: &Grid, count: &[u16], params: &PaintParams) -> f32
|
|
|
|
|
{
|
|
|
|
|
let r2 = brush_radius * brush_radius;
|
|
|
|
|
let mut gain = 0i32;
|
|
|
|
|
let mut loss = 0i32;
|
|
|
|
|
let mut bg_delta = 0i32; // bg_in_new - bg_in_old (positive = more wasted brush outside)
|
|
|
|
|
let cx = (p_old.0 + p_new.0) * 0.5;
|
|
|
|
|
let cy = (p_old.1 + p_new.1) * 0.5;
|
|
|
|
|
let dx = p_new.0 - p_old.0;
|
|
|
|
|
let dy = p_new.1 - p_old.1;
|
|
|
|
|
let half_dist = ((dx * dx + dy * dy).sqrt()) * 0.5;
|
|
|
|
|
let search_r = (brush_radius + half_dist).ceil() as i32;
|
|
|
|
|
for ddy in -search_r..=search_r {
|
|
|
|
|
for ddx in -search_r..=search_r {
|
|
|
|
|
let px = cx as i32 + ddx;
|
|
|
|
|
let py = cy as i32 + ddy;
|
|
|
|
|
let lx = px - grid.bx;
|
|
|
|
|
let ly = py - grid.by;
|
|
|
|
|
if lx < 0 || ly < 0 || lx >= grid.width || ly >= grid.height { continue; }
|
|
|
|
|
let idx = (ly * grid.width + lx) as usize;
|
|
|
|
|
|
|
|
|
|
let dx_old = px as f32 - p_old.0; let dy_old = py as f32 - p_old.1;
|
|
|
|
|
let in_old = dx_old * dx_old + dy_old * dy_old <= r2;
|
|
|
|
|
let dx_new = px as f32 - p_new.0; let dy_new = py as f32 - p_new.1;
|
|
|
|
|
let in_new = dx_new * dx_new + dy_new * dy_new <= r2;
|
|
|
|
|
if !in_old && !in_new { continue; }
|
|
|
|
|
|
|
|
|
|
if grid.was_ink[idx] {
|
|
|
|
|
if !grid.unpainted[idx] { continue; } // covered by prior stroke
|
|
|
|
|
let c_old = count[idx];
|
|
|
|
|
let c_new = c_old as i32 - in_old as i32 + in_new as i32;
|
|
|
|
|
if c_old == 0 && c_new > 0 { gain += 1; }
|
|
|
|
|
if c_old > 0 && c_new == 0 { loss += 1; }
|
|
|
|
|
} else {
|
|
|
|
|
// Background pixel under brush.
|
|
|
|
|
if in_new && !in_old { bg_delta += 1; }
|
|
|
|
|
if !in_new && in_old { bg_delta -= 1; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
gain as f32 - loss as f32 - params.bg_penalty * bg_delta as f32
|
|
|
|
|
let mut combined: Vec<(f32, f32)> = Vec::with_capacity(forward.len() + backward.len());
|
|
|
|
|
for &p in backward.iter().rev() { combined.push(p); }
|
|
|
|
|
for &p in forward.iter().skip(1) { combined.push(p); }
|
|
|
|
|
combined
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Top-level compute ───────────────────────────────────────────────────
|
|
|
|
|
@@ -2013,9 +1748,6 @@ mod tests {
|
|
|
|
|
println!(" brush_radius_percentile = {:.2}", best.params.brush_radius_percentile);
|
|
|
|
|
println!(" step_size_factor = {:.2}", best.params.step_size_factor);
|
|
|
|
|
println!(" walk_bg_penalty = {:.2}", best.params.walk_bg_penalty);
|
|
|
|
|
println!(" polish_iters = {}", best.params.polish_iters);
|
|
|
|
|
println!(" polish_search_factor = {:.2}", best.params.polish_search_factor);
|
|
|
|
|
println!(" bg_penalty = {:.2}", best.params.bg_penalty);
|
|
|
|
|
println!(" min_component_factor = {:.2}", best.params.min_component_factor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -2066,12 +1798,6 @@ mod tests {
|
|
|
|
|
set: |p, v| p.brush_radius_percentile = v, get: |p| p.brush_radius_percentile },
|
|
|
|
|
Axis { name: "brush_radius_offset_px", lo: 0.0, hi: 1.0, is_int: false,
|
|
|
|
|
set: |p, v| p.brush_radius_offset_px = v, get: |p| p.brush_radius_offset_px },
|
|
|
|
|
Axis { name: "polish_iters", lo: 0.0, hi: 12.0, is_int: true,
|
|
|
|
|
set: |p, v| p.polish_iters = v as u32, get: |p| p.polish_iters as f32 },
|
|
|
|
|
Axis { name: "polish_search_factor", lo: 0.10, hi: 4.0, is_int: false,
|
|
|
|
|
set: |p, v| p.polish_search_factor = v, get: |p| p.polish_search_factor },
|
|
|
|
|
Axis { name: "bg_penalty", lo: 0.0, hi: 20.0, is_int: false,
|
|
|
|
|
set: |p, v| p.bg_penalty = v, get: |p| p.bg_penalty },
|
|
|
|
|
Axis { name: "walk_bg_penalty", lo: 0.0, hi: 20.0, is_int: false,
|
|
|
|
|
set: |p, v| p.walk_bg_penalty = v, get: |p| p.walk_bg_penalty },
|
|
|
|
|
Axis { name: "overpaint_penalty", lo: 0.0, hi: 0.5, is_int: false,
|
|
|
|
|
@@ -2180,17 +1906,15 @@ mod tests {
|
|
|
|
|
// Same hand-picked diverse-brush seeds as before.
|
|
|
|
|
let mut s = base.clone();
|
|
|
|
|
s.brush_radius_factor = 0.55; s.brush_radius_percentile = 0.85;
|
|
|
|
|
s.min_component_factor = 1.20; s.polish_iters = 4;
|
|
|
|
|
s.min_component_factor = 1.20;
|
|
|
|
|
starts.push(s);
|
|
|
|
|
let mut s = base.clone();
|
|
|
|
|
s.brush_radius_factor = 1.00; s.brush_radius_offset_px = 0.5;
|
|
|
|
|
s.brush_radius_percentile = 0.99; s.min_component_factor = 0.20;
|
|
|
|
|
s.polish_iters = 2;
|
|
|
|
|
starts.push(s);
|
|
|
|
|
let mut s = base.clone();
|
|
|
|
|
s.brush_radius_factor = 1.15; s.brush_radius_offset_px = 0.5;
|
|
|
|
|
s.brush_radius_percentile = 0.99; s.min_component_factor = 0.20;
|
|
|
|
|
s.polish_iters = 1;
|
|
|
|
|
starts.push(s);
|
|
|
|
|
for i in 0..N_RANDOM_STARTS {
|
|
|
|
|
let mut state = (i as u64).wrapping_mul(0x9E3779B97F4A7C15).wrapping_add(0xDEADBEEF);
|
|
|
|
|
@@ -2244,9 +1968,6 @@ mod tests {
|
|
|
|
|
println!(" overpaint_penalty = {:.3} (default {:.3})", current.overpaint_penalty, base.overpaint_penalty);
|
|
|
|
|
println!(" walk_bg_penalty = {:.2} (default {:.2})", current.walk_bg_penalty, base.walk_bg_penalty);
|
|
|
|
|
println!(" min_score_factor = {:.3} (default {:.3})", current.min_score_factor, base.min_score_factor);
|
|
|
|
|
println!(" polish_iters = {} (default {})", current.polish_iters, base.polish_iters);
|
|
|
|
|
println!(" polish_search_factor = {:.2} (default {:.2})", current.polish_search_factor, base.polish_search_factor);
|
|
|
|
|
println!(" bg_penalty = {:.2} (default {:.2})", current.bg_penalty, base.bg_penalty);
|
|
|
|
|
println!(" min_component_factor = {:.2} (default {:.2})", current.min_component_factor, base.min_component_factor);
|
|
|
|
|
println!(" output_rdp_eps = {:.2} (default {:.2})", current.output_rdp_eps, base.output_rdp_eps);
|
|
|
|
|
|
|
|
|
|
@@ -2720,7 +2441,7 @@ mod tests {
|
|
|
|
|
|
|
|
|
|
let mut summary = String::new();
|
|
|
|
|
writeln!(summary, "# Brush-Paint Alphabet Report\n").unwrap();
|
|
|
|
|
writeln!(summary, "Defaults: percentile-sized brush, bg_penalty=2.0 (polish only)\n").unwrap();
|
|
|
|
|
writeln!(summary, "Defaults: percentile-sized brush, walker-only (no polish, no Dijkstra repaint)\n").unwrap();
|
|
|
|
|
|
|
|
|
|
for &(font_mm, dpi, thick) in scales {
|
|
|
|
|
writeln!(summary, "\n## font={}mm dpi={} thickness={}px\n", font_mm, dpi, thick).unwrap();
|
|
|
|
|
|