mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-25 10:27:07 -06:00
* 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>