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>
* Adjustment dialogs: preview through a clipped adjustment layer on the GPU (#77)
While an Image › Adjustments dialog is open on a pixel layer, the canvas shows the
document with a temporary adjustment layer (the dialog's settings, the selection as
its mask) clipped to the target instead of re-running the command on a CPU proxy.
The canvas compositor draws it, so on the GPU canvas a change recomposites only the
target's area within the selection, and of that only what the view shows (the rest
catches up as the view moves); zoomed out on a large document it composites a copy
reduced to about screen resolution, built once per session. The base document and
region are built once per session; a change swaps the layer's adjustment in a
shallow copy. OK still runs the real command (one history step); Cancel drops it.
Kept on the CPU proxy where the layer composite can't match the command: non-pixel
or clipped targets, Blend If, visible untouched area, channel / Quick Mask targets,
CMYK/Lab/other modes, colour-making kinds in Grayscale, and documents the GPU
compositor doesn't cover. The navigator waits for settings to settle.
Tests: preview vs command within 1/255 (worst 0.37/255) for all 13 kinds at
8/16/32-bit, RGB and Gray, with/without selection; nothing changes outside the
region; dialog flow (cache, keys, damage, Cancel untouched, OK one step); GPU
parity of the preview and of the zoomed-out proxy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* interactive_bench: per-change Image › Adjustments dialog preview rows (#77)
Times the old CPU proxy preview against the clipped-layer preview at full
resolution, at 100 % view and zoomed out, with and without a selection.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Compose: render in place and in bands; area-reduced and proxy thumbnails
render_tiled wrote every tile into a Vec and then copied them into the output:
two full-size float copies (6.3 GB for a 14000² document). Tiles are now
written straight into the output band by band.
New streaming APIs so large documents never need a full-size float
composite: render_bands (sink per band of ~8 MP), render_reduced (banded
area average, bit-identical to the old flatten-then-average thumbnail) and
flatten_to_surface (Flatten Image, Merge Visible, Copy Merged and the other
flattened-surface users).
thumbnail() reduces a downsampled proxy (moved from ui-egui to
compose::proxy, now also scaling spot channels) for documents over 16 MP
when that is faithful (no layer effects or vector masks), so navigator,
channel and histogram thumbnails of a 196 MP document cost milliseconds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* GPU canvas: request the adapter's real limits; banded CPU fallback; navigator thumbnail
egui-wgpu asks for an 8192 px texture limit, so the wgpu compositor rejected
every document wider or taller than 8192 px and each refresh fell back to a
full CPU flatten. The desktop and web apps now request the adapter's own
max_texture_dimension_2d and max_buffer_size (gpu_canvas::use_adapter_limits);
the benches use the same limits.
The CPU fallback (documents beyond the GPU limit, or features it lacks) is
GpuCanvas::refresh: it renders and uploads the composite band by band
instead of a full float composite plus a full RGBA8 copy. The CPU canvas
texture is reduced in bands too, and the Navigator on the GPU canvas uses a
cached thumbnail instead of a full-resolution CPU composite per revision.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* IO: banded export and import; no full-size copies in encoders; larger decode limits
- Flat export renders the composite in bands and quantises each band; alpha
is dropped in place when every pixel is opaque. The single-layer native
path copies surface rows a band at a time (no f32 read_region and no
per-pixel Vec). Buffers come from try_reserve: Err, not an abort.
- PSD export encodes the merged image band by band into its planes (no
flatten, no second plane copy); near-opaque documents are re-encoded
without the white matte so the output is unchanged. Layer interleaved
bytes are freed before their planes are compressed.
- Flat import converts in ~32 MB bands (and not at all when already RGBA);
the PSD merged-image un-matte works per tile row.
- Encoders borrow the image when no conversion is needed (Image::converted)
instead of cloning it; PNG only copies 16-bit data for the byte swap.
- Decode limits: 2^30 pixels and 8 GiB on 64-bit targets (a 20000² JPEG
was refused at 2^28).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Filters: write tile results in memory-bounded groups
apply_tiled collected every tile's float result before writing any (3.1 GB
for a 14000² layer). Results are now produced and written in groups of about
256 MB (at least one tile per core).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* History: bound pixel memory by Preferences > Performance > Memory Usage
History::max_bytes caps the current document's tiles plus the tiles only
history holds (layers, masks, alpha channels); History::trim drops the
oldest states beyond it after every recorded step and when preferences
change, always keeping the last step undoable. The engine sets it from
memory_usage_mb, which was unused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Large-image benchmark (14k / 20k): open, refresh, thumbnails, filter, stroke, saves
One operation per run on a cached synthetic photo, with the GPU device created
the way the app creates it, so /usr/bin/time -l reports that operation's peak
memory. Documented in docs/development.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.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>
Harris+BRIEF/RANSAC panorama with bundle adjustment, seams and multi-band
blending (Auto-Align/Auto-Blend share it); Debevec-Malik HDR merge with
ghost removal and tone mapping; Brown-Conrady lens correction (filter,
batch, smart filter); wide-angle constraint mesh warp; Crop and
Straighten Photos; Camera Raw Filter (Basic, curves, mixer, grading,
detail, effects) with dialog; Vanishing Point planes (engine only).
Fixes warp_triangles tile binning.
Checkpoint also includes compiling in-progress work of the remaining agents.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gradients/Patterns/Styles/Shapes/Tool Presets/Clone Source panels with
headless preset commands and a Custom Shape tool; Color card tabs match
Essentials. Character and Paragraph Styles (doc model, .pcraft, PSD style
sheets), Glyphs panel + type.insertText, Check Spelling (SCOWL-50 list).
Checkpoint also includes compiling in-progress work of the remaining agents.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>