HDR→SDR fails open on default builds: PQ/HLG→sRGB silently clips highlights (ConvertError not #[non_exhaustive])
- Dominant language
- Rust
- Stars
- 1
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
Verified in source, not a stale claim. Violates "the users pixels are sacred".
## The hole
`zenpixels-convert/src/convert.rs:560` gates the refusal behind the feature:
```rust
#[cfg(feature = "hdr-experimental")]
if is_hdr_to_sdr(from.transfer(), to.transfer()) {
return Err(whereat::at!(ConvertError::HdrSourceRequiresPeak { from, to }));
}
```
On a **default** (non-`hdr-experimental`) build the guard does not exist, so a PQ/HLG→sRGB `RowConverter` takes the no-tone-map arms and **hard-clips every highlight to 1.0, silently**. The comment at `:553-559` states this is deliberate — *"the historic pass-through behavior is preserved as a deliberate semi-compatibility shim for legacy non-HDR builds"* — but a silent clip is exactly what the zero-tolerance rule prohibits.
## Root cause is semver, not design
`ConvertError` is **not** `#[non_exhaustive]`, so the `HdrSourceRequiresPeak` variant cannot exist on the default build without a breaking change. That is why the guard is feature-gated rather than unconditional. The same blocker is already acknowledged in two TODOs:
- `zenpixels-convert/src/output.rs:105-107` — *"TODO(0.3.0): Add HdrPolicy enum and ConvertOutputOptions here once ConvertError is #[non_exhaustive] and can carry HdrTransferRequiresToneMapping"*
- `output.rs:230-231` — same, for the HDR→SDR policy gate.
## Runner-up (same file)
`convert.rs:664-666`: the gamut step is skipped when **either** sides primaries are `Unknown`:
```rust
let need_primaries = from.primaries != to.primaries
&& from.primaries != ColorPrimaries::Unknown
&& to.primaries != ColorPrimaries::Unknown;
```
so wide-gamut pixels tagged `Unknown` are silently relabeled as sRGB. More defensible (you cannot convert *from* an unknown gamut) — but refusing may beat relabeling.
## Ask
Make `ConvertError` `#[non_exhaustive]` in the 0.3.0 batch, then make the HDR→SDR guard unconditional: tone-map or refuse, never silent-clip. Until then the default build has a silent precision-loss path.
Found while auditing the encode surface; the in-flight `zencodec/docs/pixel-descriptor-negotiation.md` §12 independently flags the same hazard.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.