mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-03 06:52:21 -06:00
Interpret Footage: Cineon Settings convert DPX and Cineon log scans
DPX and Cineon footage showed its log code values as stored, with no way to convert them. Interpret Footage now has the Cineon Settings that After Effects' help page describes: Logarithmic Conversion, the Converted Black and White Points, the 10 Bit Black and White Points, Current Gamma, Highlight Rolloff and Units. With Logarithmic Conversion on, the code values of each decoded frame are converted from printing density to the Current Gamma between the converted points, before Separate Fields, Frame Blending, Interpret As Linear Light and the working space use them. With it off (the default), the values stay as stored. file.interpretFootage and file.interpretProxy take the settings as parameters and report them. They return an error for out-of-range values, and for Cineon parameters when none of the items is a DPX or Cineon file. Apply Interpretation copies the settings between DPX and Cineon files.
This commit is contained in:
@@ -1266,7 +1266,7 @@ impl<'a> Renderer<'a> {
|
||||
if let Some(pf) = self.proxy_for(*item)
|
||||
&& let Some(nc) = self.project.comp(*item)
|
||||
{
|
||||
let img = self.footage.frame_with(*item, pf, lt, None, self.pipe.float_encoding())?;
|
||||
let img = cineon_frame(self.footage.frame_with(*item, pf, lt, None, self.pipe.float_encoding())?, pf);
|
||||
return Some(self.footage_buf(&img, pf, nc.width, nc.height));
|
||||
}
|
||||
// Essential Properties overrides render the nested comp with this instance's
|
||||
@@ -1377,16 +1377,17 @@ impl<'a> Renderer<'a> {
|
||||
if f.fields != effectcraft_project::FieldOrder::Off && matches!(f.kind, FootageKind::Video | FootageKind::Sequence) {
|
||||
let field_rate = FrameRate::new(f.frame_rate.num * 2, f.frame_rate.den);
|
||||
let i = field_rate.frame_at(t).max(0);
|
||||
let img = self.footage.frame_with(item, f, f.frame_rate.tick_of(i / 2), None, self.pipe.float_curve(f))?;
|
||||
let img = cineon_frame(self.footage.frame_with(item, f, f.frame_rate.tick_of(i / 2), None, self.pipe.float_curve(f))?, f);
|
||||
let dominant_upper = f.fields == effectcraft_project::FieldOrder::UpperFirst;
|
||||
// Upper field = even lines (0, 2, …).
|
||||
let parity = if (i % 2 == 0) == dominant_upper { 0 } else { 1 };
|
||||
return Some(Arc::new(interpret_pixels(&field_frame(&img, parity), f, self.pipe.linear_footage_space(f))));
|
||||
}
|
||||
// Interpretation is per pixel but not linear: it runs on the frame before resampling.
|
||||
let size = size.filter(|_| !(f.invert_alpha || f.linear_light_applies()));
|
||||
let per_pixel = f.invert_alpha || f.linear_light_applies();
|
||||
let size = size.filter(|_| !(per_pixel || f.cineon_applies()));
|
||||
let img = self.footage_frame_raw(ctx, layer, item, f, t, size)?;
|
||||
if f.invert_alpha || f.linear_light_applies() { Some(Arc::new(interpret_pixels(&img, f, self.pipe.linear_footage_space(f)))) } else { Some(img) }
|
||||
if per_pixel { Some(Arc::new(interpret_pixels(&img, f, self.pipe.linear_footage_space(f)))) } else { Some(img) }
|
||||
}
|
||||
|
||||
fn footage_frame_raw(&self, ctx: &EvalCtx, layer: &Layer, item: ItemId, f: &Footage, t: Tick, size: Option<(u32, u32)>) -> Option<Arc<Image>> {
|
||||
@@ -1400,6 +1401,10 @@ impl<'a> Renderer<'a> {
|
||||
use std::hash::{Hash, Hasher};
|
||||
let mut h = std::collections::hash_map::DefaultHasher::new();
|
||||
(0x00f8_a3e5_u64, item, i, (w as f32).to_bits(), mode as u8, f.frame_rate.num, f.frame_rate.den, &f.path, float).hash(&mut h);
|
||||
if f.cineon_applies() {
|
||||
let c = &f.cineon;
|
||||
[c.converted_black, c.converted_white, c.black, c.white, c.gamma, c.rolloff].map(f32::to_bits).hash(&mut h);
|
||||
}
|
||||
h.finish()
|
||||
});
|
||||
if let (Some(c), Some(k)) = (self.cache, key)
|
||||
@@ -1407,8 +1412,9 @@ impl<'a> Renderer<'a> {
|
||||
{
|
||||
return Some(Arc::new(b.img.clone()));
|
||||
}
|
||||
let a = self.footage.frame_with(item, f, f.frame_rate.tick_of(i), None, float);
|
||||
let b = self.footage.frame_with(item, f, f.frame_rate.tick_of(i + 1), None, float);
|
||||
// Log scans are converted before they blend: the conversion is not linear.
|
||||
let a = self.footage.frame_with(item, f, f.frame_rate.tick_of(i), None, float).map(|a| cineon_frame(a, f));
|
||||
let b = self.footage.frame_with(item, f, f.frame_rate.tick_of(i + 1), None, float).map(|b| cineon_frame(b, f));
|
||||
if let (Some(a), Some(b)) = (a, b) {
|
||||
let img = blend_frames(mode, &a, &b, w as f32);
|
||||
if let (Some(c), Some(k)) = (self.cache_to_keep(), key) {
|
||||
@@ -1418,7 +1424,7 @@ impl<'a> Renderer<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
self.footage.frame_with(item, f, t, size, float)
|
||||
self.footage.frame_with(item, f, t, size, float).map(|img| cineon_frame(img, f))
|
||||
}
|
||||
|
||||
/// Source → masks, clamped/quantised to the project depth.
|
||||
@@ -2312,6 +2318,23 @@ pub fn field_frame(img: &Image, parity: usize) -> Image {
|
||||
out
|
||||
}
|
||||
|
||||
/// Interpret Footage ▸ Cineon Settings ▸ Logarithmic Conversion converts the log code values of
|
||||
/// a decoded (premultiplied) frame of `f` to the Current Gamma. When the conversion does not
|
||||
/// apply, the frame is returned unchanged.
|
||||
pub fn cineon_frame(img: Arc<Image>, f: &Footage) -> Arc<Image> {
|
||||
if !f.cineon_applies() {
|
||||
return img;
|
||||
}
|
||||
let cs = f.cineon;
|
||||
let mut out = (*img).clone();
|
||||
out.data.par_iter_mut().for_each(|p| {
|
||||
let a = p[3];
|
||||
let c = if a > 1e-6 { [p[0] / a, p[1] / a, p[2] / a] } else { [0.0; 3] }.map(|v| cs.convert(v));
|
||||
*p = [c[0] * a, c[1] * a, c[2] * a, a];
|
||||
});
|
||||
Arc::new(out)
|
||||
}
|
||||
|
||||
/// Interpret Footage ▸ Invert Alpha and Interpret As Linear Light on decoded (premultiplied)
|
||||
/// pixels; linear light is encoded in `space` ([`color::Pipe::linear_footage_space`]).
|
||||
pub fn interpret_pixels(img: &Image, f: &Footage, space: effectcraft_color::ColorSpace) -> Image {
|
||||
|
||||
Reference in New Issue
Block a user