Files
photocraft/crates/codecs/tests
Michael Higgins 9961f91a13 codecs: charge deep OpenEXR per-pixel bookkeeping against max_alloc
## Summary

`decode_deep` (deep OpenEXR, `deepscanline` / `deeptile`) admitted the data window at four bytes a pixel per channel, then built 24 bytes a pixel of bookkeeping that `max_alloc` never saw: a `u32` count per pixel, per-chunk `Vec<usize>` pixel indices and `Vec<u32>` prefix tables kept until the second pass, and the returned `u64` index. A 4096x4096 deep file of 65 to 300 KB held 384 MiB live under a 64 or 128 MiB budget and returned `Ok`.

This change:

- keeps one per-pixel structure, the cumulative `u64` index the result returns anyway (`DeepImage::counts`, 8 B/px plus one entry), allocated fallibly and **charged against `max_alloc` before anything else is allocated**;
- has each chunk record only its block bounds, parses the count tables straight into that index, and places each block row's samples as one contiguous range in the second pass (a block row is a run of consecutive raster pixels), so no per-chunk per-pixel vectors remain;
- admits the count tables (with their scratch copies) and the sample buffers against what the index leaves of the budget, so the live total stays within `max_alloc`;
- requires the offset table to list exactly one chunk per block (a duplicate or missing chunk was already malformed), so `seen` and the chunk list stay proportional to the offset table in the file rather than to the header's geometry;
- allocates the sample channels fallibly (`alloc_zeroed`) instead of with `vec!`.

Existing tests that pin exact budgets (`deep_tile_count_table_is_admitted_against_max_alloc`, `clipped_count_tables_include_scratch_in_the_budget`, `raw_count_tables_need_no_scratch_even_with_compression`) now add the index's size to their boundaries; their table-and-scratch semantics are unchanged.

Closes #9785

## Tests

- New `deep_exr::deep_bookkeeping_is_charged_against_max_alloc` (`crates/codecs/tests/it/deep_exr.rs`): a synthetic 256x256 one-channel deep image without samples, scan lines and one tile, NONE / RLE / ZIPS. Under a four-byte-a-pixel budget both `decode_deep_exr` and `decode_with` must return `LimitExceeded`; it decodes at exactly index + count table (+ scratch) and fails one byte below.
  - Before the fix: **fails** (`tile None, compression 0: a four-byte-a-pixel budget gave Ok(65537)`).
  - After the fix: passes.
- Allocation audit (counting global allocator; local only, not committed since it needs `unsafe`), 4096x4096, one float channel, ZIPS, zero counts:

  | File | max_alloc | Before | After |
  |---|---|---|---|
  | deepscanline, 307,525 bytes | 64 MiB | `Ok`, peak 384 MiB (24.0 B/px), largest request 128 MiB | `LimitExceeded` (index needs 134,217,736 bytes), peak 0 MiB |
  | deeptile, 65,597 bytes | 128 MiB | `Ok`, peak 384 MiB (24.0 B/px), largest request 128 MiB | `LimitExceeded` (index needs 134,217,736 bytes), peak 0 MiB |

- `cargo test -p photocraft-codecs --test it -- deep exr`: 81 passed.
- `cargo test -p photocraft-codecs --lib exr`: 3 passed.
- `cargo clippy -p photocraft-codecs --lib --tests -- -D warnings`: clean.
- `cargo fmt -p photocraft-codecs -- --check`: clean.

## Not covered

- The second pass still holds one chunk's decompressed sample data (and its byte-split scratch copy) next to the sample buffers; each is bounded by the samples already admitted, but the sum is not charged as one total.
- `flatten` (the flat `decode` path) admits its output and display-stage buffers one at a time while the deep image is still live.


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **Bug Fixes**
  * Improved handling of deep EXR images when memory limits are configured. Decoding now accounts for additional sample-count data when checking available memory, and rejects inputs whose required allocations exceed the limit.
  * Improved validation of deep-image count tables, including tables associated with tiled images.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-11-11 07:32:55 +01:00
..