What happens today
JxlEncoder::with_encoded_metadata (which with_metadata delegates to) routes a present ICC
profile through the encoder's colour dial:
// crates/gamut-jxl/src/encoder.rs
if let Some(icc) = &encoded.icc {
self = self.with_color(ColorSpec::Icc(icc.clone()));
}
and with_color is a plain assignment (self.color = color;). So
let e = JxlEncoder::new()
.with_color(ColorSpec::Srgb) // the caller's explicit choice
.with_metadata(&meta)?; // meta.icc = Some(profile)
// e.color is now ColorSpec::Icc(profile); the Srgb choice is gone, silently.
In every other format wired to the facade the ICC carrier is a container payload (a JPEG APP2
run, a PNG iCCP chunk, a WebP ICCP chunk), so routing it to the raw setter overwrites only a
previous profile. In JPEG XL the profile is the codestream's colour encoding, so the same
routing overwrites a semantic choice the caller made through a different API.
The fork
Three defensible rules, and the crate has never chosen between them in writing:
- Last write wins (today's behaviour) —
with_metadata is a setter like any other.
- Refuse the conflict — return
Error::InvalidInput when self.color is not the default and
the model carries a profile, so the caller orders the two calls deliberately.
- Metadata never touches the colour dial — the ICC field of an
EncodedMetadata is ignored by
JPEG XL, and a caller who wants the profile encoded says with_color(ColorSpec::Icc(..)).
Rule 2 is the only one that cannot lose information, but it makes an ordering of two independent
builder calls significant, which is its own surprise. Rule 3 makes the facade's round-trip lossy
for the one format where the profile matters most.
Whichever is chosen, the same question exists for any later format whose colour encoding and ICC
carrier are the same thing.
Interim
The behaviour is documented as it stands — in JxlEncoder::with_metadata's rustdoc and in
gamut-metadata's README ("Precedence, as it behaves today") — so a caller is not surprised by it.
Nothing is being changed until this is decided.
Evidence
Refs #420.
What happens today
JxlEncoder::with_encoded_metadata(whichwith_metadatadelegates to) routes a present ICCprofile through the encoder's colour dial:
and
with_coloris a plain assignment (self.color = color;). SoIn every other format wired to the facade the ICC carrier is a container payload (a JPEG APP2
run, a PNG
iCCPchunk, a WebPICCPchunk), so routing it to the raw setter overwrites only aprevious profile. In JPEG XL the profile is the codestream's colour encoding, so the same
routing overwrites a semantic choice the caller made through a different API.
The fork
Three defensible rules, and the crate has never chosen between them in writing:
with_metadatais a setter like any other.Error::InvalidInputwhenself.coloris not the default andthe model carries a profile, so the caller orders the two calls deliberately.
EncodedMetadatais ignored byJPEG XL, and a caller who wants the profile encoded says
with_color(ColorSpec::Icc(..)).Rule 2 is the only one that cannot lose information, but it makes an ordering of two independent
builder calls significant, which is its own surprise. Rule 3 makes the facade's round-trip lossy
for the one format where the profile matters most.
Whichever is chosen, the same question exists for any later format whose colour encoding and ICC
carrier are the same thing.
Interim
The behaviour is documented as it stands — in
JxlEncoder::with_metadata's rustdoc and ingamut-metadata's README ("Precedence, as it behaves today") — so a caller is not surprised by it.Nothing is being changed until this is decided.
Evidence
crates/gamut-jxl/src/encoder.rs—with_encoded_metadata,with_color.at the close of that pull request.
Refs #420.