Liquify dabs were spaced a fifth of the brush radius apart and restarted
at every pointer event, so a fast mouse (few far-apart events) and a slow
one (many close ones) warped the same path differently, and a slow drag
piled up one dab per event. Dabs now fall every twentieth of the radius
by distance travelled along the whole stroke, carried from one segment
to the next, as in Photoshop: any sampling of a path gives the same
field. The stationary tools' per-dab strength is scaled to the new
spacing so their effect per distance travelled is unchanged.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Rasterize Extrude blocks and scatter Wind streaks once
* Prepare Spin Blur rotations and fixed-channel sampling
* Share Lens Correction interpolation across color and alpha
* Make wide prepress traps independent of radius
* Add full-operation timing for five imaging algorithms
* Record 24 full-operation benchmarks and exact-output checks
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Reuse Facet quadrants and halftone dots without changing pixels
* Record full-image Pixelate timings and exact-output checks
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Quick Selection is now one min cut around the brush footprint (Boykov & Jolly 2001; coarse to fine for
large windows, Lombaert et al. 2005), with costs fitted to Photoshop 25.4's masks measured as a black box
on 105 synthetic and public-domain cases (mean IoU 0.94 synthetic, 0.83 paintings), at Photoshop's
512x512 working-resolution rule. Replaces the colour-model flood, which selected far too much.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Radial Blur prepares its sample transforms once per filter (blur::radial::Plan) and shares them across output tiles, reading each bilinear tap once with channels kept together. Output is identical to the previous sampler at Draft, Good and Best, across colour models and depths (tests compare against the unchanged reference path); a failed table allocation falls back to that sampler. Measured on 24 MP RGBA8, Good, amount 1: Spin 66.1 -> 16.4 s, Zoom 19.2 -> 5.4 s (bench_radial example, docs/radial-blur-performance.md).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shape Blur now builds its span kernel once per operation instead of per tile. It folds
premultiplication into the f64 row prefix sums and adds whole output rows at a time, in the
same order as before. Output is bit-identical to the previous implementation, which stays in
the tests as an exact oracle. A 24 MP Circle r25 runs in 0.65 s instead of 1.04 s.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Add the Red Eye tool to the J-group
Click a red pupil to neutralize the flash reflection. Pupil Size
sets the search window around the click; Darken Amount controls how
far corrected pixels go toward a darker, desaturated pupil.
Catchlights stay white. A miss reports an error instead of a silent
no-op. The kernel only reads a window plus a 5 px mask-filter halo.
* paint.redEye: write back only changed pixels, so CMYK/Lab stay untouched
write_rgba re-encoded every pixel of the click window through the naive
RGBA round trip, which regenerates K in CMYK and shifts Lab values. Now
untouched pixels keep their original channel data; only pixels whose RGB
changed are re-encoded. Adds a CMYK test checking untouched pixels inside
the window stay bit-exact.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* algo::redeye: cargo fmt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Polish catalog rows for the Red Eye tool; regenerate docs/scorecard.md
pl.tsv landed on main and the coverage tests need every catalog to translate new UI strings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The neighbourhood radius was a 97 px constant (3σ+1 at σ=32) while softness
can drive σ to 96, so tiles seamed once the short side reached about 1600 px.
Halo is now 3σ+1 plus the finite-difference step, taken from the document's
σ (or the 96 cap when size is unknown).
The kernel used to copy the input unchanged above 8 MP of luma. Large rects
are split into overlapping bands instead, so an 8 MP+ layer is actually relit.
Replace the O(nσ) separable kernel with the three-box Gaussian already used
for other smoothing fields in this crate.
Blurs run as box passes now filter the whole area at once instead of tile by tile: the
horizontal passes run over the source rows only and the vertical passes over column strips,
with the canvas edge repeated virtually instead of materialising a 3-sigma margin around every
tile (at radius 500 that margin was ~25 MP of floats per 4 MP tile, copied and transposed
several times). The results are written to the layer's tile columns in parallel.
Output matches the previous running sums (8-bit: identical except 114 of 2.1 M samples one
level apart; 16-bit: within 2/65535) and a direct-convolution oracle at every depth.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
CI's `cargo fmt --all -- --check` has failed on every run because the
repo had no rustfmt config and was never formatted. Use the same settings
as EffectCraft/FilmCraft (max_width 160, use_small_heuristics Max), which
also keeps the churn small. Formatting only; no code changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>