* Reuse Facet quadrants and halftone dots without changing pixels * Record full-image Pixelate timings and exact-output checks --------- Co-authored-by: Brandon Thomas <[email protected]>
4.8 KiB
Facet and Color Halftone performance
2026-10-10. Two changes remove repeated work from the existing Pixelate filters:
- Facet: calculate luma once per source pixel and calculate each 3x3 quadrant once. A three-row ring shares that quadrant with its four output pixels. The nine-sample addition order, variance calculation, quadrant order and strict tie comparison stay the same.
- Color Halftone: memoize the five-tap ink calculation and dot radius in the rotated screen grid, separately for each channel. Pixels still use the same candidate dots, distances and anti-alias coverage. Lab conversion, CMYK ink direction and alpha handling stay the same.
Facet uses about 291 KiB of additional scratch for a 256x256 output tile. The luma plane is capped at 1,048,576 samples. Halftone tables are capped at 2 MiB per channel. Scratch allocations are fallible; an incomplete halo, oversized table or unsuitable screen coordinates uses the original calculation. The default tiled filter path stays parallel on native targets and single-threaded on wasm.
Full filter timings
EC2 m7i.4xlarge, 16 vCPUs (Intel Xeon Platinum 8488C), Ubuntu 24.04, rustc 1.99.0, default release profile, RAYON_NUM_THREADS=16. Base: 7722172585a01cbdb93c06f0f5ff2634fcb17999. The changed algorithms are in 1bc3a9f1. Before and after were built in separate target directories; no other builds or benchmarks ran during measurement.
Each entry is the median of three alternating pairs: before/after, after/before, before/after. The timer covers apply_in, including tile reads, filtering, assembly, depth conversion, writeback and pruning. Fixture construction, the small warmup and output serialization are outside the timer. The fixture is a dense deterministic native-color image with varying alpha, negative document origin and transparent pixels. These are CPU filter application times; window presentation and undo bookkeeping are outside this measurement.
| Filter | Size | Model + alpha | Depth | Before (ms) | After (ms) | Speedup |
|---|---|---|---|---|---|---|
| Facet | 24 MP | RGB | u8 | 684.4 | 311.5 | 2.20x |
| Halftone r8 | 24 MP | RGB | u8 | 1137.3 | 603.8 | 1.88x |
| Facet | 36 MP | RGB | u8 | 1027.5 | 454.5 | 2.26x |
| Halftone r8 | 36 MP | RGB | u8 | 1707.8 | 898.0 | 1.90x |
| Facet | 24 MP | RGB | u16 | 756.9 | 370.1 | 2.04x |
| Halftone r8 | 24 MP | RGB | u16 | 1198.9 | 664.7 | 1.80x |
| Facet | 24 MP | RGB | f32 | 806.9 | 435.7 | 1.85x |
| Halftone r8 | 24 MP | RGB | f32 | 1272.0 | 725.6 | 1.75x |
| Halftone r2 | 24 MP | RGB | u8 | 1417.8 | 716.4 | 1.98x |
| Halftone r64 | 24 MP | RGB | u8 | 1138.9 | 587.5 | 1.94x |
| Facet | 24 MP | CMYK | u8 | 7888.4 | 574.2 | 13.74x |
| Halftone r8 | 24 MP | LAB | u8 | 3588.7 | 718.3 | 5.00x |
Every pair was compared with cmp over the complete output, serialized as little-endian f32 sample bits in tile order. All 36 comparisons matched exactly, including the 16-bit and float cases. Raw three-run timings and output SHA-256 hashes are committed alongside this table.
Reproduce
Build the same example against the base and the changed tree. Keep their Cargo target directories separate:
git worktree add --detach ../photocraft-pixelate-base 7722172585a01cbdb93c06f0f5ff2634fcb17999
cp crates/algo/examples/bench_pixelate.rs ../photocraft-pixelate-base/crates/algo/examples/
(cd ../photocraft-pixelate-base && CARGO_TARGET_DIR=target cargo build --release -p photocraft-algo --example bench_pixelate)
CARGO_TARGET_DIR=target cargo build --release -p photocraft-algo --example bench_pixelate
RAYON_NUM_THREADS=16 ../photocraft-pixelate-base/target/release/examples/bench_pixelate facet 6000 4000 u8 1 before.bin rgb
RAYON_NUM_THREADS=16 target/release/examples/bench_pixelate facet 6000 4000 u8 1 after.bin rgb
cmp before.bin after.bin
sha256sum before.bin after.bin
Repeat each pair three times, reversing the order for the middle pair. Use 7200 5000 for 36 MP; u16 or f32 for the other depths; halftone, halftone-small or halftone-large for the screen cases; and cmyk or lab for the other color models. The example prints the timed operation separately from file writing.
Correctness and repository checks
The independent scalar oracles retain the original kernels. Tests compare every sample bit across all eight color models, alpha on/off, constant colors and edges, random samples, HDR/negative values, non-finite samples, thin regions, partial halos, extreme screen offsets, extended channel counts and scratch rejection. Tiled surface tests add 8/16/32-bit storage, soft selections, tile sizes 1/7/17/256 and both transparent and repeated edges.
Passed: 401 algorithm tests; clippy with warnings denied; formatting; dependency layering; the full cargo xtask wasm set; cargo xtask perf --quick on llvmpipe; and scorecard generation. No command, UI or format contracts changed.