mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-23 05:55:55 +02:00
* Arrow-key nudges: a run of nudges is one History state, with Tools preferences (#2259) Tapping or holding an arrow key nudged the selected layers one pixel per History state, so one Undo took back one pixel. Nudges of the same thing that follow each other now share one `coalesce` key (the engine already folds edits with the same key into one step), so a run is one state and one Undo takes it back. - nudge_bundle.rs decides which run a nudge belongs to: the previous nudge was of the same thing (layers, selection outline), came within the pause, and left the document as it is now (no other edit, Undo/Redo, layer selection or save in between). - move_mods.rs runs layer.translate and select.transformSelection through it; Alt-nudges (a copy each) never join a run. Selected pixels float until dropped, which is one state already. - Free Transform keeps its own session history; its box nudges fold into one session step (transform_tool.rs). - Tools preferences: Bundle Arrow-Key Nudges (default on; off gives a state per press as before) and Nudge Bundle Pause (ms) (default 1000, 100..10000, hidden while bundling is off). Labels translated in all catalogues. The two #1428 tests that asserted a state per press for the selection outline now cover both settings. * Regenerate docs/scorecard.md after merging main --------- Co-authored-by: Brandon Thomas <echelon@gmail.com>
315 lines
19 KiB
TOML
315 lines
19 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 = "done"
|
||
issue = 213
|
||
note = "Global: Preferences › Tools › Pressure Curve (prefs tools.pressureCurve; applied to every pen sample in ui-egui stylus.rs before any brush; editor pressure_curve_ui.rs with Linear/Soft/Hard presets). Per control: every brush Dynamic has its own curve (paint ResponseCurve, the same monotone cubic; Dynamic.curve, set by tools.setBrush, e.g. shapeDynamics.size.curve) applied to its Fade/Pressure/Tilt/Wheel/Rotation input in dynamics.rs dynamic_value; Brush Settings shows it behind each Control row's curve button (#3294)"
|
||
|
||
[[item]]
|
||
id = "TOOL-213-12"
|
||
target = "Import an image file as a brush tip"
|
||
status = "done"
|
||
issue = 213
|
||
note = "brush.presets.importImage (path or data); Brushes panel and preset picker Import Brushes… take .abr or an image (#3279)"
|
||
|
||
[[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 = "done"
|
||
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); Shift-click puts the next anchor on the nearest 45 deg ray from the previous one and Shift-drag turns the handles to 45 deg steps, live (vector_ui.rs pen_shift_click_puts_the_next_anchor_on_a_45_degree_ray, pen_shift_drag_turns_handles_to_45_degrees_live, #3751)"
|
||
|
||
[[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 = "On-canvas corner radius widgets on every selected live rectangle with Path Selection, the Rectangle tool and Move + Show Transform Controls: drag rounds every corner of every selected rectangle alike, Alt that corner on each, corner radius cursor and R: readout, one history step (corner_radius.rs, 2026-10-13); select.convertToShape with tolerance (vector_cmds.rs:1256). Missing: shape feather"
|
||
|
||
[[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-2259-1"
|
||
target = "Arrow-key nudges of one thing (layers, the selection outline, the Free Transform box) that follow each other are one History state, with a preference to turn it off"
|
||
status = "done"
|
||
issue = 2259
|
||
note = "nudge_bundle.rs: a run shares one `coalesce` key while each nudge comes within Tools > Nudge Bundle Pause (1 s) and nothing else touched the document (an edit, Undo/Redo, a layer selection, a save end it); move_mods.rs runs layer.translate and select.transformSelection through it, transform_tool.rs folds box nudges into one session step. Tools > Bundle Arrow-Key Nudges off gives every press its own state. Selected pixels float until dropped, which is one state already; ⌥-nudges (a copy each) never join a run"
|
||
|
||
[[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"
|