diff --git a/crates/script/src/model.rs b/crates/script/src/model.rs index 72c2880b..6948ea72 100644 --- a/crates/script/src/model.rs +++ b/crates/script/src/model.rs @@ -479,6 +479,9 @@ fn value(s: &Session, a: &J) -> R { } else { let mut ctx = EvalCtx::new(&s.project, cid, c, t); ctx.expr = s.expr.as_deref(); + // Sample expressions (`sampleImage`) read footage through the session's source; without + // it footage layers sample as transparent (#1000). + ctx.footage = Some(s.footage.as_ref()); ctx.value(l, p) }; Ok(js_value(c, &v)) diff --git a/crates/script/src/tests.rs b/crates/script/src/tests.rs index bfffdee5..71d3843f 100644 --- a/crates/script/src/tests.rs +++ b/crates/script/src/tests.rs @@ -813,6 +813,42 @@ fn footage_proxies_sequences_and_conform_rate() { let _ = std::fs::remove_dir_all(&dir); } +/// A flat green footage source, so `sampleImage` on a footage layer has known pixels. +struct Flat; +impl effectcraft_engine::render::FootageSource for Flat { + fn frame( + &self, + _item: effectcraft_engine::project::ItemId, + f: &effectcraft_engine::project::Footage, + _t: effectcraft_engine::time::Tick, + ) -> Option> { + Some(Arc::new(effectcraft_engine::render::Image::filled(f.width.max(1), f.height.max(1), [0.0, 1.0, 0.0, 1.0]))) + } +} + +/// `sampleImage` on a footage layer reads the footage's pixels from a script, not transparent +/// black: the expression evaluator needs the session's footage source (#1000). +#[test] +fn sample_image_reads_footage_pixels_in_scripts() { + let mut s = session(); + s.footage = Arc::new(Flat); + s.importer = Some(Arc::new(Stills)); + let out = ok( + &mut s, + r#" + var comp = app.project.items.addComp("Scene", 640, 360, 1, 1, 25); + comp.openInViewer(); + var img = comp.layers.add(app.project.importFile(new ImportOptions(new File("/tmp/ec-flat.png")))); + img.name = "Img"; + var cc = comp.layers.addNull().property("ADBE Effect Parade").addProperty("ADBE Color Control").property(1); + cc.expression = 'thisComp.layer("Img").sampleImage([32, 16], [0.5, 0.5], true, time);'; + var v = cc.valueAtTime(0, false); + [v[0], v[1], v[2], v[3], cc.expressionError]; + "#, + ); + assert_eq!(out.result, json!([0, 1, 0, 1, ""]), "green footage sampled, not transparent black"); +} + #[test] fn binary_file_io_preserves_all_byte_values() { let dir = std::env::temp_dir().join(format!("ec-script-binary-{}", std::process::id()));