use ffcine_core::lut::Lut;
use crate::shaders::LookParams;
pub const DEFAULT_SIZE: usize = 65;
#[derive(Debug, Clone, Default)]
pub struct Bake<'a> {
pub camera: Option<&'a Lut>,
pub lut: Option<&'a Lut>,
pub look: LookParams,
}
impl Bake<'_> {
pub fn eval(&self, rgb: [f32; 3]) -> [f32; 3] {
let mut v = rgb;
for l in [self.camera, self.lut].into_iter().flatten() {
v = l.sample(v);
}
if self.look.is_identity() {
v
} else {
self.look.apply(v)
}
}
pub fn bake(&self, size: usize) -> Lut {
let mut lut = Lut::identity(size.max(2));
for e in &mut lut.data {
*e = self.eval(*e);
}
let parts: Vec<&str> = [self.camera, self.lut]
.into_iter()
.flatten()
.map(|l| l.title.as_deref().unwrap_or("LUT"))
.chain((!self.look.is_identity()).then_some("look"))
.collect();
lut.title = Some(format!("ffcine bake: {}", parts.join(" + ")));
lut
}
}
#[cfg(test)]
mod tests {
use super::*;
fn mono(f: impl Fn(f32) -> f32) -> Lut {
let mut l = Lut::identity(17);
for e in &mut l.data {
*e = e.map(&f);
}
l
}
#[test]
fn identity_bakes_to_identity() {
let b = Bake::default().bake(9);
assert_eq!(b.data, Lut::identity(9).data);
}
#[test]
fn chain_order_and_accuracy() {
let cam = mono(|x| x * x);
let grade = mono(|x| 0.5 + 0.5 * x);
let look = LookParams {
saturation: 0.8,
contrast: 0.1,
..LookParams::default()
};
let b = Bake {
camera: Some(&cam),
lut: Some(&grade),
look,
};
let baked = b.bake(DEFAULT_SIZE);
for v in [[0.1, 0.4, 0.9], [0.33, 0.33, 0.7], [0.95, 0.05, 0.5]] {
let want = b.eval(v);
let got = baked.sample(v);
for c in 0..3 {
assert!(
(want[c] - got[c]).abs() < 0.01,
"{v:?}: {want:?} vs {got:?}"
);
}
}
let plain = Bake {
camera: Some(&cam),
lut: Some(&grade),
look: LookParams::default(),
};
assert!((plain.eval([0.5; 3])[0] - 0.625).abs() < 0.01);
assert!(baked.title.unwrap().ends_with("+ look"));
}
}