mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-30 13:08:07 -06:00
Interpret Footage: the Footage panel, thumbnails and tracking show Cineon Settings
The Footage panel, the Project panel thumbnail and the native-size source frames that tracking, mask tracking, scene detection and auto-trace read took DPX and Cineon frames straight from the file, so they showed the log code values while a composition showed the converted ones. Each now applies the footage's Cineon Settings, as the composition renderer does.
This commit is contained in:
@@ -56,6 +56,12 @@ pub fn timecode(item: Option<&effectcraft_project::Item>, fr: effectcraft_time::
|
||||
}
|
||||
}
|
||||
|
||||
/// The frame the Footage panel shows: footage `f` at source time `t`, with its Cineon Settings
|
||||
/// applied.
|
||||
pub fn frame(s: &Session, item: ItemId, f: &effectcraft_project::Footage, t: Tick) -> Option<std::sync::Arc<effectcraft_raster::Image>> {
|
||||
s.footage.frame(item, f, t).map(|img| effectcraft_render::cineon_frame(img, f))
|
||||
}
|
||||
|
||||
fn source_time(s: &Session, p: &Value, item: ItemId) -> Option<Tick> {
|
||||
let (_, fr) = source_timing(s, item);
|
||||
if let Some(t) = f_p(p, "time") {
|
||||
|
||||
@@ -1131,7 +1131,7 @@ impl Session {
|
||||
effectcraft_project::ItemKind::Footage(f) if f.has_video => {
|
||||
let f = f.clone();
|
||||
Some(Box::new(move || {
|
||||
let img = footage.frame(item, &f, Tick(0))?;
|
||||
let img = effectcraft_render::cineon_frame(footage.frame(item, &f, Tick(0))?, &f);
|
||||
// Point-sample down to at most `max_side` on the long side.
|
||||
let step = (img.width.max(img.height) as f32 / max_side.max(1) as f32).ceil().max(1.0) as u32;
|
||||
let (w, h) = (img.width.div_ceil(step), img.height.div_ceil(step));
|
||||
|
||||
@@ -385,3 +385,32 @@ fn cineon_settings_convert_dpx_code_values() {
|
||||
s.execute_checked("file.interpretProxy", json!({"items": [inner.0], "logConversion": true, "highlightRolloff": 0})).unwrap();
|
||||
assert!(near(proxied(&s, 0), 0.0) && near(proxied(&s, 1), 1.0), "{} {}", proxied(&s, 0), proxied(&s, 1));
|
||||
}
|
||||
|
||||
/// The Footage panel, the Project panel thumbnail and the tracker's source frames show DPX
|
||||
/// footage with its Cineon Settings, as a composition renders it.
|
||||
#[test]
|
||||
fn cineon_settings_apply_to_footage_panel_thumbnail_and_tracking() {
|
||||
let mut s = session();
|
||||
let d = import(&mut s, "/m/scan.dpx");
|
||||
s.execute("comp.new", json!({"name": "C", "width": 4, "height": 4, "frameRate": 10, "duration": 1})).unwrap();
|
||||
let c = s.active_comp_id().unwrap();
|
||||
s.execute("layer.addItem", json!({"item": d.0})).unwrap();
|
||||
s.execute("file.interpretFootage", json!({"item": d.0, "logConversion": true, "highlightRolloff": 0})).unwrap();
|
||||
let near = |a: f32, b: f32| (a - b).abs() < 1.5 / 255.0;
|
||||
let rendered = px(&s, c, 0.0, RenderOpts::default(), 2, 0)[0];
|
||||
assert!(near(rendered, 0.26190), "code 400 at gamma 1.7: {rendered}");
|
||||
let f = match &s.project.item(d).unwrap().kind {
|
||||
ItemKind::Footage(f) => f.clone(),
|
||||
_ => panic!("not footage"),
|
||||
};
|
||||
let panel = crate::commands::footage_panel::frame(&s, d, &f, Tick::ZERO).unwrap().get(2, 0)[0];
|
||||
assert!(near(panel, rendered), "Footage panel: {panel}");
|
||||
let (w, _, rgba) = (s.thumbnail_job(d, Tick::ZERO, 64).unwrap())().unwrap();
|
||||
assert_eq!(w, 4);
|
||||
let thumb = rgba[8] as f32 / 255.0;
|
||||
assert!(near(thumb, rendered), "Project panel thumbnail: {thumb}");
|
||||
let comp = s.project.comp(c).unwrap();
|
||||
let r = crate::render::Renderer::new(&s.project, s.footage.as_ref(), RenderOpts::default());
|
||||
let (img, _) = crate::tracking::source_frame(&r, &s.project, c, comp, &comp.layers[0], Tick::ZERO, None).unwrap();
|
||||
assert!(near(img.get(2, 0)[0], rendered), "tracker source frame: {}", img.get(2, 0)[0]);
|
||||
}
|
||||
|
||||
@@ -197,7 +197,8 @@ pub(crate) struct Work {
|
||||
}
|
||||
|
||||
/// The layer's source frame at comp time `t` at 100 % (layer pixels + offset = image pixels).
|
||||
/// Footage at its native size is used straight from the footage source (no copy).
|
||||
/// Footage at its native size is used straight from the footage source (no copy unless its
|
||||
/// Cineon Settings convert it).
|
||||
pub(crate) fn source_frame(
|
||||
r: &Renderer,
|
||||
project: &Project,
|
||||
@@ -215,7 +216,7 @@ pub(crate) fn source_frame(
|
||||
&& img.width == f.width
|
||||
&& img.height == f.height
|
||||
{
|
||||
return Some((img, [0.0; 2]));
|
||||
return Some((effectcraft_render::cineon_frame(img, f), [0.0; 2]));
|
||||
}
|
||||
let buf = r.layer_source(&ctx, layer)?;
|
||||
if (buf.scale - 1.0).abs() > 1e-9 && buf.scale > 0.0 {
|
||||
|
||||
@@ -62,7 +62,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
let cached: Option<((u64, i64, u64), egui::TextureHandle, [u32; 2])> = ctx.data(|d| d.get_temp(tid));
|
||||
let tex = match cached {
|
||||
Some((k, tex, size)) if k == key => Some((tex, size)),
|
||||
_ => app.session.footage.frame(view.item, f, view.time).map(|img| {
|
||||
_ => effectcraft_engine::commands::footage_panel::frame(&app.session, view.item, f, view.time).map(|img| {
|
||||
let ci = egui::ColorImage::from_rgba_unmultiplied([img.width as usize, img.height as usize], &img.to_rgba8());
|
||||
let tex = crate::frames::load_fitted(&ctx, "footage-panel", ci, egui::TextureOptions::LINEAR);
|
||||
ctx.data_mut(|d| d.insert_temp(tid, (key, tex.clone(), [img.width, img.height])));
|
||||
|
||||
Reference in New Issue
Block a user