Lower the axial body threshold while retaining fringe rejection, minimize length-only tip inset, and make sheet sensitivity curves testable and conservative at zero.
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { blackKeep, greenKeep } from './sheetKeep.ts';
|
||||
|
||||
// --- Black sheet sensitivity (#74) ---
|
||||
|
||||
test('blackKeep: s=0 keeps dark live-edge wood (lum ~25–50), removes near-black sheet (#74)', () => {
|
||||
// Near-black sheet (lum ≈ 5)
|
||||
assert.equal(blackKeep(5, 5, 5, 0), false, 'near-black sheet removed at s=0');
|
||||
assert.equal(blackKeep(0, 0, 0, 0), false, 'pure black sheet removed at s=0');
|
||||
// Dark wood / bark / live edge
|
||||
assert.equal(blackKeep(25, 22, 18, 0), true, 'lum~22 dark wood kept at s=0');
|
||||
assert.equal(blackKeep(40, 35, 30, 0), true, 'lum~35 dark wood kept at s=0');
|
||||
assert.equal(blackKeep(50, 45, 40, 0), true, 'lum~46 wood kept at s=0');
|
||||
// Mid wood always kept
|
||||
assert.equal(blackKeep(120, 90, 60, 0), true, 'mid wood kept at s=0');
|
||||
});
|
||||
|
||||
test('blackKeep: s=100 removes more than s=0; curve monotonic (#74)', () => {
|
||||
// Dark-ish pixel that sits between floors: sheet at high s, wood at low s
|
||||
const darkish = { r: 80, g: 70, b: 60 }; // lum ≈ 72
|
||||
assert.equal(blackKeep(darkish.r, darkish.g, darkish.b, 0), true, 'lum~72 kept at s=0');
|
||||
assert.equal(blackKeep(darkish.r, darkish.g, darkish.b, 100), false, 'lum~72 sheet at s=100');
|
||||
|
||||
// Near-black always sheet
|
||||
assert.equal(blackKeep(2, 2, 2, 0), false);
|
||||
assert.equal(blackKeep(2, 2, 2, 100), false);
|
||||
|
||||
// Bright wood always kept
|
||||
assert.equal(blackKeep(200, 160, 100, 0), true);
|
||||
assert.equal(blackKeep(200, 160, 100, 100), true);
|
||||
|
||||
// Monotonic: once removed, stays removed as s increases
|
||||
const probe = { r: 60, g: 55, b: 50 }; // lum ≈ 56
|
||||
let prevKeep = true;
|
||||
for (const s of [0, 25, 50, 75, 100]) {
|
||||
const keep = blackKeep(probe.r, probe.g, probe.b, s);
|
||||
if (!prevKeep) assert.equal(keep, false, `once sheet at lower s, stay sheet at s=${s}`);
|
||||
prevKeep = keep;
|
||||
}
|
||||
// s=100 must remove more pixels than s=0 for this mid-dark band
|
||||
assert.equal(blackKeep(probe.r, probe.g, probe.b, 0), true);
|
||||
assert.equal(blackKeep(probe.r, probe.g, probe.b, 100), false);
|
||||
});
|
||||
|
||||
test('greenKeep: s=0 does not eat mild wood greens; s=100 widens sheet match (#74)', () => {
|
||||
// Neutral wood
|
||||
assert.equal(greenKeep(140, 110, 80, 0), true);
|
||||
assert.equal(greenKeep(140, 110, 80, 100), true);
|
||||
// Mild green cast on wood (g - max(r,b) ≈ 5) — must keep at s=0
|
||||
assert.equal(greenKeep(100, 105, 90, 0), true, 'mild green cast kept at s=0');
|
||||
// Strong sheet green
|
||||
assert.equal(greenKeep(40, 180, 50, 0), false, 'strong green sheet removed at s=0');
|
||||
assert.equal(greenKeep(40, 180, 50, 100), false);
|
||||
// Borderline: g-max ≈ 20 — may become sheet at high s
|
||||
const border = { r: 80, g: 100, b: 70 }; // delta = 20
|
||||
assert.equal(greenKeep(border.r, border.g, border.b, 0), true, 'delta20 kept at s=0');
|
||||
assert.equal(greenKeep(border.r, border.g, border.b, 100), false, 'delta20 sheet at s=100');
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Sheet-vs-slab keep predicates used by the mask worker.
|
||||
* Extracted so unit tests can cover sensitivity curves without the worker.
|
||||
*/
|
||||
|
||||
/** Keep slab pixels on a green sheet. Higher s widens the green-sheet match. */
|
||||
export function greenKeep(r: number, g: number, b: number, s: number): boolean {
|
||||
const t = Math.max(0, Math.min(100, s)) / 100;
|
||||
// s=0 removes only strongly green sheet; increasing sensitivity widens the match.
|
||||
const isSheet = g - Math.max(r, b) > 45 - 35 * t;
|
||||
return !isSheet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep slab pixels on a black sheet.
|
||||
* At s=0 only near-black sheet (very low lum) is removed so dark live-edge /
|
||||
* bark (lum ~25–50) stays. The match widens monotonically toward s=100 (#74).
|
||||
*/
|
||||
export function blackKeep(r: number, g: number, b: number, s: number): boolean {
|
||||
const t = Math.max(0, Math.min(100, s)) / 100;
|
||||
const lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
// Old floor 40 ate dark wood at minimum sensitivity. Floor 10 isolates near-black.
|
||||
const isSheet = lum < 10 + 140 * t;
|
||||
return !isSheet;
|
||||
}
|
||||
|
||||
export function autoPickSheet(
|
||||
samples: { r: number; g: number; b: number }[],
|
||||
): 'green' | 'black' {
|
||||
let gr = 0, gl = 0;
|
||||
for (const p of samples) {
|
||||
if (p.g - Math.max(p.r, p.b) > 20) gr++;
|
||||
if (0.2126 * p.r + 0.7152 * p.g + 0.0722 * p.b < 70) gl++;
|
||||
}
|
||||
return gl >= gr ? 'black' : 'green';
|
||||
}
|
||||
Reference in New Issue
Block a user