PSD/PSB open without reading the whole file: channel data read as it is decoded, in bands straight into tiles (#5106)

Opening a 2.14 GB PSD (2000×2000, 109 layers, 821 MB of embedded smart objects) peaked at
7.28 GB and took 6.8 s; it now peaks at 3.00 GB and takes 3.6 s (macOS peak memory footprint,
`cargo run --release -p photocraft-io --example psd_open_mem`).

- photocraft-psd: `PsdFile::from_source` parses from a `ReadAt` source (`FileSource`: pread /
  seek_read in bounded reads). Channel data and the merged image become `Stored` ranges read
  when decoded; the model equals `from_bytes`'s and writes back the same bytes (tests compare
  the two on every generated file, including Lr16/Lr32 layer blocks and truncated files).
- `ChannelData::row_decoder` decodes any band of rows: RLE via its row table (a stored channel
  reads only the band's bytes), raw by offset, ZIP inflated once.
- photocraft-io: `import_file` opens a path; PSD/PSB through `from_source`, other formats read
  whole. Layers decode in bands of whole tile rows (~32 MB) into tiles: no whole-layer plane or
  interleaved buffer. Global blocks move into the document instead of being copied; finding an
  embedded smart object no longer copies every file in `lnk2`.
- photocraft-raster: `Surface::share_uniform_tiles` + `TileInterner`; the PSD import shares
  single-value tiles (backgrounds, fill caches, masks) across layers.
- The background open job and the CLI/MCP (`automation::files::open`) use `import_file`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Phil Trinh
2026-10-16 18:25:59 +08:00
parent 938542c78b
commit 4915f0970e
30 changed files with 1646 additions and 122 deletions
Generated
+1
View File
@@ -4827,6 +4827,7 @@ dependencies = [
"photocraft-engine",
"photocraft-format",
"photocraft-io",
"photocraft-raster",
"photocraft-raw",
"rmcp",
"rustix 1.1.5",