mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-10 18:31:30 -06:00
EXR export: write the output space's chromaticities attribute (#3038)
* EXR export: write the output space's chromaticities attribute (#3037) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * EXR chromaticities: tests for unmanaged and PQ ProEXR, every space once Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * EXR export: note why the comp background is added before decoding (review point) CodeRabbit asked to convert Comp.background to the output space before compositing. Every output path (Image::to_rgba8_over) adds the background to the output-encoded frame as it is, so the EXR now matches the other outputs; converting would make the EXR colour differ from the PNG of the same comp. Comment only. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Format the merged code with rustfmt Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
ee7c51740d
commit
ae068ff931
@@ -160,7 +160,8 @@ pub fn layered_exr(s: &Session, cid: ItemId, t: Tick, scale: f64) -> Result<(Vec
|
||||
// The composite and every layer without the HDR step, decoded with the output space's
|
||||
// curve (linear light in the output space's primaries).
|
||||
let (full, layers) = isolate(s, cid, t, scale, true)?;
|
||||
let dec = effectcraft_render::color::Pipe::of(&s.project.settings).output_linear();
|
||||
let pipe = effectcraft_render::color::Pipe::of(&s.project.settings);
|
||||
let dec = pipe.output_linear();
|
||||
let (w, h) = (full.width as usize, full.height as usize);
|
||||
let planes = |img: &effectcraft_raster::Image| -> [Vec<f32>; 4] {
|
||||
let mut out: [Vec<f32>; 4] = Default::default();
|
||||
@@ -192,7 +193,11 @@ pub fn layered_exr(s: &Session, cid: ItemId, t: Tick, scale: f64) -> Result<(Vec
|
||||
}
|
||||
let layer = Layer::new((w, h), LayerAttributes::default(), Encoding::SMALL_LOSSLESS, AnyChannels::sort(channels.into()));
|
||||
let mut buf = std::io::Cursor::new(Vec::new());
|
||||
Image::from_layer(layer).write().to_buffered(&mut buf).map_err(|e| EngineError::Other(format!("EXR encoding failed: {e}")))?;
|
||||
let mut image = Image::from_layer(layer);
|
||||
// The primaries and white the values are in (the output space's), for Nuke, AE and Blender.
|
||||
let [red, green, blue, white] = pipe.output_linear_space().exr_chromaticities().map(|[x, y]| Vec2(x, y));
|
||||
image.attributes.chromaticities = Some(exr::meta::attribute::Chromaticities { red, green, blue, white });
|
||||
image.write().to_buffered(&mut buf).map_err(|e| EngineError::Other(format!("EXR encoding failed: {e}")))?;
|
||||
Ok((buf.into_inner(), names, full.width, full.height))
|
||||
}
|
||||
|
||||
|
||||
@@ -358,3 +358,28 @@ fn proexr_layers_are_not_tone_mapped() {
|
||||
assert!((at(n) - want).abs() <= 1e-4 * want, "{n}: {} vs {want}", at(n));
|
||||
}
|
||||
}
|
||||
|
||||
/// The `chromaticities` attribute of the ProEXR file `path`: red, green, blue, white (CIE xy).
|
||||
fn proexr_chromaticities(s: &mut Session, name: &str) -> Option<[[f32; 2]; 4]> {
|
||||
let path = out_dir(name).join("frame.exr").to_string_lossy().to_string();
|
||||
s.execute("comp.saveFrameAsExr", json!({"path": path})).unwrap();
|
||||
let meta = exr::meta::MetaData::read_from_buffered(std::io::BufReader::new(std::fs::File::open(&path).unwrap()), false).unwrap();
|
||||
meta.headers[0].shared_attributes.chromaticities.map(|c| [c.red, c.green, c.blue, c.white].map(|v| [v.0, v.1]))
|
||||
}
|
||||
|
||||
/// ProEXR names the primaries and white of the output space (default: Rec. 709 / sRGB).
|
||||
#[test]
|
||||
fn proexr_writes_the_output_spaces_chromaticities() {
|
||||
let d65 = [0.3127, 0.3290];
|
||||
let mut s = frame();
|
||||
assert_eq!(proexr_chromaticities(&mut s, "frame-exr-chroma-default"), Some([[0.64, 0.33], [0.30, 0.60], [0.15, 0.06], d65]));
|
||||
s.execute("file.projectSettings", json!({"workingSpace": "srgb", "outputSpace": "p3"})).unwrap();
|
||||
assert_eq!(proexr_chromaticities(&mut s, "frame-exr-chroma-p3"), Some([[0.680, 0.320], [0.265, 0.690], [0.150, 0.060], d65]));
|
||||
s.execute("file.projectSettings", json!({"outputSpace": "rec2020"})).unwrap();
|
||||
assert_eq!(proexr_chromaticities(&mut s, "frame-exr-chroma-2020"), Some([[0.708, 0.292], [0.170, 0.797], [0.131, 0.046], d65]));
|
||||
s.execute("file.projectSettings", json!({"outputSpace": "rec2100pq"})).unwrap();
|
||||
assert_eq!(proexr_chromaticities(&mut s, "frame-exr-chroma-pq"), Some([[0.708, 0.292], [0.170, 0.797], [0.131, 0.046], d65]));
|
||||
// Unmanaged: the output space is ignored, the Working Gamma's curve (Rec. 709 primaries) decodes.
|
||||
s.execute("file.projectSettings", json!({"workingSpace": "none", "outputSpace": "p3"})).unwrap();
|
||||
assert_eq!(proexr_chromaticities(&mut s, "frame-exr-chroma-unmanaged"), Some([[0.64, 0.33], [0.30, 0.60], [0.15, 0.06], d65]));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user