From 7a0e60c1047567e37b4693aa182578f63f9a43b6 Mon Sep 17 00:00:00 2001 From: Nicolas Falesy <112726756+nicolasfalesy@users.noreply.github.com> Date: Wed, 14 Oct 2026 01:59:13 -0400 Subject: [PATCH] test(engine): add Color Range fuzziness 0 exact-match test (TOOL-214-3) (#3581) * test(engine): add Color Range fuzziness 0 exact-match test (#214) Adds an explicit test for Sampled Colors at Fuzziness 0 (point sampling and hex colors) asserting that exact matches are fully selected and adjacent shades are rejected. Updates scorecard TOOL-214-3 to done. * docs: regenerate scorecard (TOOL-214-3 done) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017WeVmjnnPPKdxVTpr2Vjtw --------- Co-authored-by: Claude Opus 5.5 --- crates/engine/src/selection_cmds.rs | 39 +++++++++++++++++++++++++++++ docs/scorecard.md | 4 +-- scorecard/tools.toml | 4 +-- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/crates/engine/src/selection_cmds.rs b/crates/engine/src/selection_cmds.rs index 74de286f0..be0639418 100644 --- a/crates/engine/src/selection_cmds.rs +++ b/crates/engine/src/selection_cmds.rs @@ -1004,6 +1004,45 @@ mod tests { assert!(lighter > redder + 0.15 && redder > 0.0, "lighter {lighter}, redder {redder}"); } + #[test] + fn color_range_fuzziness_zero_selects_exact_match_only() { + // Sampled Colors at Fuzziness 0 selects only the exact match (TOOL-214-3, #214). + let mut s = Session::new(); + s.execute("file.new", json!({"width": 50, "height": 10})).unwrap(); + s.edit("paint", |doc, _| { + let bg = doc.layers[0].surface_mut().unwrap(); + let v = |c: u8| f32::from(c) / 255.0; + // Exact target color [200, 140, 120] + bg.fill_rect(Rect::new(0, 0, 10, 10), &[v(200), v(140), v(120), 1.0]); + // Off by 1 level in red [201, 140, 120] + bg.fill_rect(Rect::new(10, 0, 20, 10), &[v(201), v(140), v(120), 1.0]); + // Off by 1 level in green [200, 141, 120] + bg.fill_rect(Rect::new(20, 0, 30, 10), &[v(200), v(141), v(120), 1.0]); + // Off by 2 levels in blue [200, 140, 122] (crosses 8-bit Lab grid threshold) + bg.fill_rect(Rect::new(30, 0, 40, 10), &[v(200), v(140), v(122), 1.0]); + // Exact target color again at a different position + bg.fill_rect(Rect::new(40, 0, 50, 10), &[v(200), v(140), v(120), 1.0]); + Ok(()) + }) + .unwrap(); + + // Sampled via eyedropper point at [5, 5] with Fuzziness 0 + s.execute("select.colorRange", json!({"points": [[5, 5]], "fuzziness": 0})).unwrap(); + assert_eq!(coverage(&s, 5, 5), 1.0, "sampled pixel itself is fully selected"); + assert_eq!(coverage(&s, 45, 5), 1.0, "identical color elsewhere on canvas is fully selected"); + assert_eq!(coverage(&s, 15, 5), 0.0, "1 level off in red is unselected at fuzziness 0"); + assert_eq!(coverage(&s, 25, 5), 0.0, "1 level off in green is unselected at fuzziness 0"); + assert_eq!(coverage(&s, 35, 5), 0.0, "1 level off in blue is unselected at fuzziness 0"); + + // Sampled via hex color with Fuzziness 0 + s.execute("select.colorRange", json!({"color": "#c88c78", "fuzziness": 0})).unwrap(); + assert_eq!(coverage(&s, 5, 5), 1.0, "exact color match from hex is fully selected"); + assert_eq!(coverage(&s, 45, 5), 1.0, "identical color elsewhere on canvas is fully selected"); + assert_eq!(coverage(&s, 15, 5), 0.0, "1 level off in red is unselected for hex at fuzziness 0"); + assert_eq!(coverage(&s, 25, 5), 0.0, "1 level off in green is unselected for hex at fuzziness 0"); + assert_eq!(coverage(&s, 35, 5), 0.0, "1 level off in blue is unselected for hex at fuzziness 0"); + } + /// Red squares (from `session`), a blue square, a black and a mid-grey strip on white. fn session_colours() -> Session { let mut s = session(); diff --git a/docs/scorecard.md b/docs/scorecard.md index 92f9315eb..56a16654b 100644 --- a/docs/scorecard.md +++ b/docs/scorecard.md @@ -8,7 +8,7 @@ Generated by `cargo xtask scorecard` from `perf/budgets.toml`, `perf/baseline.js |---|---:|---:|---:|---| | [Performance](#performance) | 3 | 22 | 22 | 56 scenarios: 3 within budget, 22 over budget or failing, 9 tracked only, 17 not run, 5 not measurable yet (baseline 2026-10-05) | | [File compatibility](#file-compatibility) | 7 | 13 | 7 | io corpus: oracle ≥ 146/170, round trip ≥ 169/170; psd-tools corpus: oracle ≥ 236/309, round trip ≥ 308/309; photoshop oracles: oracle ≥ 145/256, round trip ≥ 258/256 | -| [Tools](#tools) | 28 | 14 | 1 | | +| [Tools](#tools) | 29 | 13 | 1 | | | [Workspace and UI](#workspace-and-ui) | 13 | 6 | 4 | settings that do nothing: 26 of 157 (target 0) | | [Type](#type) | 4 | 3 | 2 | | | [Automation](#automation) | 5 | 2 | 4 | 631 of 631 menu items live (100.0%) | @@ -147,7 +147,7 @@ Corpus floors (`crates/io/tests/corpus.rs`): the enforced minimums; the corpus t | TOOL-213-18 | Pattern Stamp tool: sibling of Clone Stamp in the S flyout | done | [#213](https://github.com/storytold/photocraft/issues/213) | Tool::PatternStamp in the S group; paint.patternStamp stamps the Patterns-panel tile with Aligned and Impressionist (an approximation: it jitters each dab's tile phase, not Photoshop's smeared impressionist look); engine tests cover depths, seams, hostile input | | TOOL-214-1 | Select and Mask workspace: view modes, Refine Edge brush, 6 outputs | partial | [#214](https://github.com/storytold/photocraft/issues/214) | select.refineEdge dialog with a live preview in Photoshop's view modes: Onion Skin, Marching Ants, Overlay, On Black, On White, Black & White, On Layers, per-view Opacity, Show Edge (J), Show Original (P), F cycles, X shows the image (select_mask_view.rs, #1754); Refine Edge Brush (R, size, Expand Detection Area / Restore Original Edge, Alt swaps) paints select.refineEdge refineStrokes, estimated by colour-line matting (matting/brush.rs, #2972); outputs Selection, Layer Mask, New Layer, New Layer with Mask, New Document, New Document with Mask (#2998); no workspace layout | | TOOL-214-2 | Marquee: Shift square, Alt from centre, live W x H readout | done | [#214](https://github.com/storytold/photocraft/issues/214) | canvas.rs marquee_corners: Shift square or circle, Alt from the centre, live W x H readout beside the cursor (marquee_readout); marquee_tests.rs | -| TOOL-214-3 | Color Range test: Sampled Colors at Fuzziness 0 selects the exact match only | partial | [#214](https://github.com/storytold/photocraft/issues/214) | Fuzziness-0 tests cover midtones/shadows only (selection_cmds.rs:444-447) | +| TOOL-214-3 | Color Range test: Sampled Colors at Fuzziness 0 selects the exact match only | done | [#214](https://github.com/storytold/photocraft/issues/214) | color_range_fuzziness_zero_selects_exact_match_only in selection_cmds.rs asserts exact match is selected and 1-level-off pixels are rejected at fuzziness 0; tonal presets covered in color_range_presets | | TOOL-214-4 | Move tool: box-select layers, Auto-Select hover outline, double-click fit, drag to a tab | partial | [#214](https://github.com/storytold/photocraft/issues/214) | Auto-Select hover outline (Preferences › Tools › Show Highlight on Rollover, #2834) and box-select layers from an empty spot (move_box.rs, layer.selectInRect, #2844) done; double-click fit and drag to a tab open | | TOOL-214-5 | Right-click menu during Free Transform (scale, rotate, skew, warp, flip, rotate 90/180) | done | [#214](https://github.com/storytold/photocraft/issues/214) | canvas_tool_menu.rs TRANSFORM_MENU: modes, Content-Aware Scale / Puppet Warp greyed, Rotate 180 / 90 CW / 90 CCW and Flip turn the open box (transform_tool::intercept, #2832) | | TOOL-214-6 | Puppet Warp mesh follows the object outline (constrained triangulation) | done | [#214](https://github.com/storytold/photocraft/issues/214) | algo/puppet.rs build_mesh: traced outline of the opaque region grown by Expansion, triangulated with a Density-spaced lattice (conforming Delaunay); separate shapes get separate meshes (#3105) | diff --git a/scorecard/tools.toml b/scorecard/tools.toml index cbff50e33..1123c6aab 100644 --- a/scorecard/tools.toml +++ b/scorecard/tools.toml @@ -127,9 +127,9 @@ note = "canvas.rs marquee_corners: Shift square or circle, Alt from the centre, [[item]] id = "TOOL-214-3" target = "Color Range test: Sampled Colors at Fuzziness 0 selects the exact match only" -status = "partial" +status = "done" issue = 214 -note = "Fuzziness-0 tests cover midtones/shadows only (selection_cmds.rs:444-447)" +note = "color_range_fuzziness_zero_selects_exact_match_only in selection_cmds.rs asserts exact match is selected and 1-level-off pixels are rejected at fuzziness 0; tonal presets covered in color_range_presets" [[item]] id = "TOOL-214-4"