mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-14 11:16:53 -04:00
* 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 <[email protected]> Claude-Session: https://claude.ai/code/session_017WeVmjnnPPKdxVTpr2Vjtw --------- Co-authored-by: Claude Opus 5.5 <[email protected]>
308 lines
18 KiB
TOML
308 lines
18 KiB
TOML
# Scorecard checklist: tools (#213 painting and retouching, #214 selection, transform, drawing).
|
||
# An item is done when it has a command, a UI and tests.
|
||
# Statuses must be true on main. Verified against 4b53f62 on 2026-10-05.
|
||
area = "tools"
|
||
title = "Tools"
|
||
order = 3
|
||
|
||
[[item]]
|
||
id = "TOOL-213-1"
|
||
target = "Pencil tool: aliased 1 px painting, square pixel-aligned cursor"
|
||
status = "done"
|
||
issue = 213
|
||
note = "Tool::Pencil (B group): live aliased strokes on the pixel grid, square cursor, Auto Erase; pencil_tests.rs"
|
||
|
||
[[item]]
|
||
id = "TOOL-213-2"
|
||
target = "Mixer Brush tool on the toolbar"
|
||
status = "done"
|
||
issue = 213
|
||
note = "Tool::MixerBrush in the Brush (B) flyout; Wet/Load/Mix/Flow/Sample All Layers options, command/control dispatch and selection-clipping tests"
|
||
|
||
[[item]]
|
||
id = "TOOL-213-3"
|
||
target = "Patch tool with a live preview of the patched area"
|
||
status = "done"
|
||
issue = 213
|
||
note = "paint.patch (retouch_cmds/patch.rs, source/destination) + Tool::Patch (J group: lasso, then drag the selection); the drag shows the heal live (patch_preview.rs, coarse colour fit: 1000x1000 in 52 ms)"
|
||
|
||
[[item]]
|
||
id = "TOOL-213-4"
|
||
target = "Content-Aware Move tool reusing inpaint completion"
|
||
status = "partial"
|
||
issue = 213
|
||
note = "paint.contentAwareMove (retouch_cmds/content_aware_move.rs: move/extend, Structure 1-7, Color 0-10, Sample All Layers, Transform On Drop's scaleX/scaleY/rotate; background job) + Tool::ContentAwareMove (J group: lasso, then drag; Transform On Drop puts a scale/rotate box on the dropped patch, cam_transform.rs: Enter commits as one step, Esc cancels). cam_preview.rs: once the pointer or the box rests, the result shows (a cancellable background job on a smaller sampling window, small selections only, #4385). Missing: a progressive preview for big selections"
|
||
|
||
[[item]]
|
||
id = "TOOL-213-5"
|
||
target = "Painting a type/shape/smart/fill layer offers to rasterize, then continues the stroke"
|
||
status = "done"
|
||
issue = 213
|
||
note = "rasterize_prompt.rs: OK rasterizes then paints (two history states), Cancel does nothing; drivable via ui.dialog.confirm"
|
||
|
||
[[item]]
|
||
id = "TOOL-213-6"
|
||
target = "Fill dialog: contents, blend, opacity, preserve transparency; remembered; Shift+Backspace"
|
||
status = "done"
|
||
issue = 213
|
||
note = "fill_ui.rs dialog over edit.fill (fill_cmds.rs): all contents, mode, opacity, preserve; remembered in prefs; Shift+F5 / Shift+Backspace"
|
||
|
||
[[item]]
|
||
id = "TOOL-213-7"
|
||
target = "Live stroke preview for every brush-based retouch tool"
|
||
status = "partial"
|
||
issue = 213
|
||
note = "Live (canvas.rs strokes_live): Brush, Pencil, Eraser, Blur, Sharpen, Smudge, Dodge, Burn, Sponge, Clone Stamp, Healing Brush, History Brush (#1026, #1040); the release commits the live stroke's pixels (P32). Not yet live: Spot Healing Brush, Mixer Brush, Background Eraser, Pattern Stamp"
|
||
|
||
[[item]]
|
||
id = "TOOL-213-8"
|
||
target = "Clone Source: W/H link, drawn overlay (opacity/blend/clip/auto-hide), Alt target cursor"
|
||
status = "partial"
|
||
issue = 213
|
||
note = "Clone Source overlay drawn with opacity, inversion, circular clipping, auto-hide and the Normal/Darken/Lighten/Difference blend against the image under the brush (canvas.rs draw_clone_preview, clone_source::overlay_blend); a shaped tip (sampled bitmap, rotation, roundness) clips the overlay to its outline (brush_cursor::tip_mask, #4372); W/H link (cloneSource.set linked, panel chain button, #3298)"
|
||
|
||
[[item]]
|
||
id = "TOOL-213-9"
|
||
target = "Eyedropper comparison ring (new colour above, current below) while sampling"
|
||
status = "done"
|
||
issue = 213
|
||
note = "eyedropper_ring (canvas.rs): the sampled colour on the upper arc, the foreground below, drawn for the Eyedropper and Alt-sampling while the button is held; the Precise-cursor preference keeps the crosshair"
|
||
|
||
[[item]]
|
||
id = "TOOL-213-10"
|
||
target = "Ctrl+Alt drag resizes brush size/hardness with an on-canvas readout"
|
||
status = "done"
|
||
issue = 213
|
||
note = "brush_resize.rs (#297, #370): Alt+right-drag, or Control+Option-drag on macOS (Control+Alt everywhere), sets diameter horizontally and hardness vertically, circle plus Diameter/Hardness readout, one coalesced tools.setBrush, no paint or history; 9 tests there"
|
||
|
||
[[item]]
|
||
id = "TOOL-213-11"
|
||
target = "Editable pressure curve, global and per control"
|
||
status = "partial"
|
||
issue = 213
|
||
note = "Global: Preferences › Tools › Pressure Curve (prefs tools.pressureCurve; monotone cubic, engine prefs::PressureCurve; applied to every pen sample in ui-egui stylus.rs before any brush; editor pressure_curve_ui.rs with Linear/Soft/Hard presets). Missing: per-control curves (each brush dynamic's own response to pressure, tilt, rotation)"
|
||
|
||
[[item]]
|
||
id = "TOOL-213-12"
|
||
target = "Import an image file as a brush tip"
|
||
status = "missing"
|
||
issue = 213
|
||
note = "Only brush.defineFromSelection and brush.presets.importAbr"
|
||
|
||
[[item]]
|
||
id = "TOOL-213-13"
|
||
target = "Brush regression tests: no seams, hard edge, zero-scatter defaults, ABR never black"
|
||
status = "done"
|
||
issue = 213
|
||
note = "Seam, hard-edge and zero-scatter tests in brush_tests.rs; ABR tip colour test in abr_map/tests.rs"
|
||
|
||
[[item]]
|
||
id = "TOOL-213-14"
|
||
target = "Tablet: no OS arrow cursor over the canvas on macOS, Windows and Linux"
|
||
status = "partial"
|
||
issue = 213
|
||
note = "Brush tools set CursorIcon::None (canvas.rs:1259-1275); not verified per OS"
|
||
|
||
[[item]]
|
||
id = "TOOL-213-18"
|
||
target = "Pattern Stamp tool: sibling of Clone Stamp in the S flyout"
|
||
status = "done"
|
||
issue = 213
|
||
note = "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"
|
||
|
||
[[item]]
|
||
id = "TOOL-214-1"
|
||
target = "Select and Mask workspace: view modes, Refine Edge brush, 6 outputs"
|
||
status = "partial"
|
||
issue = 214
|
||
note = "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"
|
||
|
||
[[item]]
|
||
id = "TOOL-214-2"
|
||
target = "Marquee: Shift square, Alt from centre, live W x H readout"
|
||
status = "done"
|
||
issue = 214
|
||
note = "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"
|
||
|
||
[[item]]
|
||
id = "TOOL-214-3"
|
||
target = "Color Range test: Sampled Colors at Fuzziness 0 selects the exact match only"
|
||
status = "done"
|
||
issue = 214
|
||
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"
|
||
target = "Move tool: box-select layers, Auto-Select hover outline, double-click fit, drag to a tab"
|
||
status = "partial"
|
||
issue = 214
|
||
note = "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"
|
||
|
||
[[item]]
|
||
id = "TOOL-214-5"
|
||
target = "Right-click menu during Free Transform (scale, rotate, skew, warp, flip, rotate 90/180)"
|
||
status = "done"
|
||
issue = 214
|
||
note = "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)"
|
||
|
||
[[item]]
|
||
id = "TOOL-214-6"
|
||
target = "Puppet Warp mesh follows the object outline (constrained triangulation)"
|
||
status = "done"
|
||
issue = 214
|
||
note = "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)"
|
||
|
||
[[item]]
|
||
id = "TOOL-214-7"
|
||
target = "Content-Aware Scale through the Free Transform box with a live proxy preview"
|
||
status = "done"
|
||
issue = 214
|
||
note = "Edit > Content-Aware Scale opens a scale-only Free Transform box (cas_transform.rs) with Amount, Protect and Protect Skin Tones in the options bar; edit.contentAwareScale carves a 384 px copy in the background for the live preview, and Enter runs it with the box's size and x/y as one step (#3096)"
|
||
|
||
[[item]]
|
||
id = "TOOL-214-8"
|
||
target = "A measured ruler angle prefills Image Rotation > Arbitrary (0.01 deg)"
|
||
status = "done"
|
||
issue = 214
|
||
note = "analysis_ui.rs: image.rotation.arbitrary opens seeded with the ruler angle and the direction that straightens it; arbitrary_rotation_starts_at_the_ruler_angle checks 5.7106 deg within 1e-3 and cw/ccw for three measured lines"
|
||
|
||
[[item]]
|
||
id = "TOOL-214-9"
|
||
target = "Crop: W x H x Resolution with units, saved crop presets, Straighten in the options bar"
|
||
status = "done"
|
||
issue = 214
|
||
note = "Ratio dropdown and fields, Straighten (TOOL-214-21); W x H x Resolution with units, built-in size presets and Front Image (crop_size.rs, image.crop targetWidth/targetHeight/resolution, #2443); New / Delete Crop Preset save and delete crop tool presets (crop_presets.rs, tool.presets.* with tool crop, persisted with the preferences)"
|
||
|
||
[[item]]
|
||
id = "TOOL-214-10"
|
||
target = "Pen: Alt breaks a handle, Shift 45 deg, Ctrl direct-selects, Backspace, close-drag handle"
|
||
status = "partial"
|
||
issue = 214
|
||
note = "Ctrl/Cmd = Direct Selection and Alt = Convert Point on existing paths (direct_select.rs, #790); Backspace/Delete retracts the last point while drawing (#1466); Alt during a drag breaks the handle pair, Alt-drag turns the last anchor's outgoing handle alone, and dragging on the first anchor closes with that anchor's handles (vector_ui.rs, #3306); no Shift 45 deg"
|
||
|
||
[[item]]
|
||
id = "TOOL-214-11"
|
||
target = "Stroke Path dialog: pick any brush-based tool, Simulate Pressure"
|
||
status = "done"
|
||
issue = 214
|
||
note = "path.stroke tool: Brush, Pencil, Eraser and every other brush-based tool (Background Eraser, Clone/Pattern Stamp, Healing/Spot Healing Brush, History Brush, Blur, Sharpen, Smudge, Dodge, Burn, Sponge, Color Replacement, Mixer Brush: their paint.* command along the path, one step) plus simulatePressure (taper both ends); Stroke Path dialog from the Pen menu, the Paths panel menu and Alt-click on its Stroke button (#2978)"
|
||
|
||
[[item]]
|
||
id = "TOOL-214-12"
|
||
target = "Shapes: on-canvas corner radius handles, shape feather, selection to shape with tolerance"
|
||
status = "partial"
|
||
issue = 214
|
||
note = "select.convertToShape with tolerance (vector_cmds.rs:1256); radius as a field only"
|
||
|
||
[[item]]
|
||
id = "TOOL-214-13"
|
||
target = "Mask Properties: Density and Feather controls"
|
||
status = "partial"
|
||
issue = 214
|
||
note = "Vector-mask density/feather in the engine (vector_cmds.rs:948); no UI controls"
|
||
|
||
[[item]]
|
||
id = "TOOL-214-14"
|
||
target = "Vanishing Point window: draw and edit planes, paste, clone in perspective"
|
||
status = "partial"
|
||
issue = 214
|
||
note = "filter.vanishingPoint (vp_cmds.rs:15); on-canvas plane editor (vp_ui.rs: Create/Edit Plane, edge nodes, valid/doubtful/invalid grid colours, Grid Size, clipboard pasted onto the plane on OK); clone stamp, marquee and tear-off planes in the window still missing"
|
||
|
||
[[item]]
|
||
id = "TOOL-214-15"
|
||
target = "Content-Aware Fill workspace: paint the sampling area, preview, rotate/scale/mirror, new layer"
|
||
status = "partial"
|
||
issue = 214
|
||
note = "Engine sampling auto, rect, custom (edit_menu_cmds.rs:324); generic dialog"
|
||
|
||
[[item]]
|
||
id = "TOOL-214-16"
|
||
target = "Liquify: redo (Cmd+Shift+Z)"
|
||
status = "done"
|
||
issue = 214
|
||
note = "liquify_ui.rs supports bounded redo through Cmd+Shift+Z and the control channel; new strokes clear redo"
|
||
|
||
[[item]]
|
||
id = "TOOL-213-17"
|
||
target = "Red Eye tool: click a pupil to neutralize the red reflection"
|
||
status = "done"
|
||
issue = 213
|
||
note = "paint.redEye (redeye_cmds.rs) + Tool::RedEye in the J flyout; Pupil Size and Darken Amount; catchlights kept; a miss is an error"
|
||
|
||
[[item]]
|
||
id = "TOOL-214-17"
|
||
target = "Rotate View tool: drag to turn the view, Reset View, Match Rotation copies the angle"
|
||
status = "done"
|
||
issue = 214
|
||
note = "Tool::RotateView in the Hand flyout; view.rotateView / view.resetView; Match Rotation copies views[].rotation; enhanced_controls.rotate_view_with_trackpad is read"
|
||
|
||
[[item]]
|
||
id = "TOOL-1649-1"
|
||
target = "Eyedropper Sample Size (Point, 3×3 … 101×101 Average), Sample layers, Show Sampling Ring"
|
||
status = "done"
|
||
issue = 1649
|
||
note = "document.sampleColor (sample_cmds.rs): average of the square clipped to the canvas, in the document's model and depth; eyedropper_ui.rs options bar; shared with Alt-click, dialog eyedroppers and the Info panel; ui.set eyedropperSampleSize/eyedropperSample/eyedropperRing. Color Sampler tool is separate (#1046, sampler_cmds.rs)"
|
||
|
||
[[item]]
|
||
id = "TOOL-214-20"
|
||
target = "Crop overlay options: Rule of Thirds, Grid, Diagonal, Triangle, Golden Ratio, Golden Spiral; Auto / Always / Never Show Overlay; O cycles the overlay, Shift+O its orientation"
|
||
status = "done"
|
||
issue = 1919
|
||
note = "crop_overlay.rs: unit-square guides shared by upright and turned frames (canvas.rs), options-bar menu, O / Shift+O ahead of the tool keys in shortcuts.rs; ToolOptions crop_overlay / crop_overlay_show / crop_overlay_orientation, ui.set cropOverlay / cropOverlayShow / cropOverlayOrientation"
|
||
|
||
[[item]]
|
||
id = "TOOL-214-18"
|
||
target = "Crop: drag outside the frame to rotate it (Shift 15 deg steps, angle readout); Enter rotates and crops in one step"
|
||
status = "done"
|
||
issue = 1792
|
||
note = "image.crop angle param (image_cmds.rs: Image Rotation > Arbitrary + crop, one undo step); crop_ui.rs turns, moves and resizes the frame along its axes (Classic Mode; default mode turns the image instead, TOOL-214-23); UiState.crop_angle"
|
||
|
||
[[item]]
|
||
id = "TOOL-214-19"
|
||
target = "Crop: arrow keys nudge the crop box (Shift 10 px), X swaps portrait/landscape, W x H readout while drawing or resizing"
|
||
status = "done"
|
||
issue = 1919
|
||
note = "crop_ui.rs keys/nudge/swap_orientation (ahead of the selection nudge and tools.swapColors in shortcuts.rs; X also swaps the options-bar ratio) and sizing (canvas.rs draws it with the marquee readout); turned frames included"
|
||
|
||
[[item]]
|
||
id = "TOOL-214-21"
|
||
target = "Crop: Straighten button in the options bar and Cmd/Ctrl-drag draw a line; the frame turns so the line ends up level (or plumb when nearer vertical), with an angle readout"
|
||
status = "done"
|
||
issue = 1919
|
||
note = "crop_straighten.rs: one-shot options-bar button, Cmd/Ctrl held is a temporary Straighten (ui.pointer command flag); the Ruler's straighten_angle convention; the frame is fitted to the largest of its proportions inside the image (Classic Mode); a pending edit committed by Enter"
|
||
|
||
[[item]]
|
||
id = "TOOL-214-22"
|
||
target = "Crop gear menu: Show Cropped Area (H), Enable Crop Shield with Match Canvas / custom colour, opacity and Auto Adjust Opacity"
|
||
status = "done"
|
||
issue = 1919
|
||
note = "crop_shield.rs: fill() resolves the shield (canvas.rs paints it around upright and turned frames), gear popup in the options bar, H ahead of the Hand key in shortcuts.rs, Custom… opens the Color Picker; ToolOptions crop_shield, ui.set cropShield. Use Classic Mode / Auto Center Preview: TOOL-214-23"
|
||
|
||
[[item]]
|
||
id = "TOOL-214-23"
|
||
target = "Crop: Photoshop's default mode (the image turns and moves behind an upright box, Auto Center Preview keeps a resized box's centre in place and centres a drawn one) and Use Classic Mode (P) in the gear menu"
|
||
status = "done"
|
||
issue = 1919
|
||
note = "crop_mode.rs: the crop model (crop_rect + crop_angle) and image.crop are shared by both modes; default mode sets the view's rotation to -crop_angle while the tool shows a frame (picking the tool, in either mode, turns a Rotate View turn upright about the view centre, not restored after Enter/Esc or by P; Classic Mode keeps the camera upright with the frame drawn turned; as Photoshop 25.1), pivoting so the box stays put, moves view and frame together for drags inside and arrow keys, keeps a resized box's centre in place live (Auto Center Preview; a drawn box is centred on release, a moved one never), readout = the image's turn; gear menu order and Classic Mode greying as Photoshop 25.1; CropShield classic_mode / auto_center_preview (ui.set cropShield), P ahead of the Pen key"
|
||
|
||
[[item]]
|
||
id = "TOOL-1053-1"
|
||
target = "Shape stroke controls: solid/dashed/dotted, custom dash/gap/offset, caps, joins, alignment, miter limit and opacity"
|
||
status = "done"
|
||
issue = 1053
|
||
note = "shape_stroke_ui.rs: shared staged editor, renderer samples, cached Properties canvas preview; existing shape.create/edit/info; serde tool defaults, ui.set shapeStroke; pcraft and vstk style round trips. Gradient fill UI and saved stroke libraries remain open; Line retains filled-bar geometry"
|
||
|
||
[[item]]
|
||
id = "TOOL-213-19"
|
||
target = "Remove tool: paint over an object (or circle it) and it is filled from its surroundings"
|
||
status = "partial"
|
||
issue = 213
|
||
note = "paint.remove (retouch_cmds/remove.rs, background job) + Tool::Remove in the J flyout; loops close over what they enclose; fill by non-local patch completion with texture features, onion-peel start, best-patch copy and gradient-domain seam hiding (algo/nonlocal.rs, Newson et al. IPOL 2017); sparse holes in tiles (algo/remove.rs). remove_quality on 20 holes in public-domain photos: the fill keeps 0.89 of the texture it replaces (0.54 with Content-Aware Fill's completion), edge gradient 1.03 of the original's; remove_bench 24 MP: 100 px ring 120 ms, 1000 px ring 2.5 s, 3000 px line 1.7 s. Options bar: Sample All Layers, Create a new layer (the fill on a new layer above, `newLayer`), Remove after each stroke off (strokes collect as a tint and ↩ applies them as one `strokes` job, Esc drops them). Missing: generative AI mode, live preview of the result"
|
||
|
||
[[item]]
|
||
id = "TOOL-1045-1"
|
||
target = "Color Replacement tool on the toolbar"
|
||
status = "done"
|
||
issue = 1045
|
||
note = "Tool::ColorReplacement in the Brush (B) flyout after the Mixer Brush; Mode/Sampling/Limits/Tolerance/Anti-alias options drive paint.colorReplacement with the foreground colour; live preview (LiveDab with the stroke's finishing dab), one History step; color_replacement_tests.rs"
|