Merge pull request #675 from vbasky/fix/458-ultra-key-setting

Apply Ultra Key Setting as a parameter preset
This commit is contained in:
Brandon Thomas
2026-10-10 14:54:27 -04:00
committed by GitHub
6 changed files with 117 additions and 27 deletions
+26 -8
View File
@@ -2585,15 +2585,33 @@ fn build() -> Vec<CommandSpec> {
{
e.params.insert(pid.clone(), filmcraft_project::Param::new(d.default.clone()));
}
let prm = crate::masks::target_param(e, &pq, &pid).ok_or_else(|| bad("effects.setParam", format!("no param `{pid}`")))?;
let v = json_to_param(&prm.value, &val).ok_or_else(|| bad("effects.setParam", "value has the wrong type"))?;
if keyframe {
// the stopwatch and the value in one step
prm.put_keyframe(mt, v);
} else {
prm.set_at(mt, v);
let (keyframes, v) = {
let prm = crate::masks::target_param(e, &pq, &pid).ok_or_else(|| bad("effects.setParam", format!("no param `{pid}`")))?;
let v = json_to_param(&prm.value, &val).ok_or_else(|| bad("effects.setParam", "value has the wrong type"))?;
let keyframes = if keyframe {
// the stopwatch and the value in one step
prm.put_keyframe(mt, v.clone());
prm.keyframes.len()
} else {
prm.set_at(mt, v.clone());
prm.keyframes.len()
};
(keyframes, v)
};
if e.effect == "ultra_key" && pid == "setting"
&& let ParamValue::Choice(setting) = v
&& let Some(rows) = filmcraft_project::effect::ultra_key_setting(setting)
{
for (id, value) in rows {
if let Some(p) = e.param_mut(id) {
p.value = ParamValue::Float(*value);
p.keyframes.clear();
} else {
e.params.insert((*id).to_string(), filmcraft_project::Param::new(ParamValue::Float(*value)));
}
}
}
Ok(prm.keyframes.len())
Ok(keyframes)
})?;
// 0 keyframes: the value is static (`time` was not used)
Ok(json!({"keyframes": keyframes}))
+13
View File
@@ -115,3 +115,16 @@ fn new_effect_params_are_settable_and_keyframable() {
let img = s.render_program(0.125).unwrap();
assert!(img.px.iter().all(|v| v.is_finite()));
}
#[test]
fn ultra_key_setting_writes_aggressive_parameters() {
let mut s = demo();
let clip = first_v1_clip(&s);
s.execute("effects.apply", json!({"clips": [clip], "effect": "ultra_key"})).unwrap();
s.execute("effects.setParam", json!({"clip": clip, "effect": "ultra_key", "param": "setting", "value": 2})).unwrap();
let e = s.active_sequence().unwrap().find_item(filmcraft_project::ClipId(clip)).unwrap().1.effect("ultra_key").unwrap();
assert_eq!(e.param("tolerance").unwrap().value.as_f64(), Some(90.0));
assert_eq!(e.param("pedestal").unwrap().value.as_f64(), Some(50.0));
assert_eq!(e.param("choke").unwrap().value.as_f64(), Some(10.0));
assert_eq!(e.param("contrast").unwrap().value.as_f64(), Some(10.0));
}
+1 -1
View File
@@ -17,7 +17,7 @@ use crate::mask::Mask;
pub use audio::{GEQ10_LABELS, GEQ20_LABELS, GEQ30_LABELS, PREMIERE_AUDIO_EFFECTS};
mod vfx;
pub use vfx::{EASINGS, ECHO_OPERATORS, FRAME_LAYOUTS, LIGHT_IDS, SIMPLE_BLEND, TRACK_CHOICES, auto_point};
pub use vfx::{EASINGS, ECHO_OPERATORS, FRAME_LAYOUTS, LIGHT_IDS, SIMPLE_BLEND, TRACK_CHOICES, auto_point, ultra_key_setting};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum EffectKind {
+54
View File
@@ -1014,6 +1014,60 @@ pub(super) fn defs() -> Vec<EffectDef> {
v
}
/// Parameter values written when Ultra Key's Setting is Default, Relaxed or Aggressive.
/// Custom (index 3) leaves the current values. Aggressive matches the matte that cleans
/// light and dark clothing on a typical green screen.
pub fn ultra_key_setting(setting: u32) -> Option<&'static [(&'static str, f64)]> {
Some(match setting {
0 => &[
("transparency", 45.0),
("highlight", 10.0),
("shadow", 50.0),
("tolerance", 50.0),
("pedestal", 10.0),
("choke", 0.0),
("soften", 0.0),
("contrast", 0.0),
("mid_point", 50.0),
("desaturate", 25.0),
("range", 50.0),
("spill", 50.0),
("spill_luma", 50.0),
],
1 => &[
("transparency", 30.0),
("highlight", 15.0),
("shadow", 35.0),
("tolerance", 70.0),
("pedestal", 5.0),
("choke", 0.0),
("soften", 8.0),
("contrast", 0.0),
("mid_point", 50.0),
("desaturate", 15.0),
("range", 50.0),
("spill", 40.0),
("spill_luma", 50.0),
],
2 => &[
("transparency", 40.0),
("highlight", 10.0),
("shadow", 55.0),
("tolerance", 90.0),
("pedestal", 50.0),
("choke", 10.0),
("soften", 10.0),
("contrast", 10.0),
("mid_point", 50.0),
("desaturate", 50.0),
("range", 50.0),
("spill", 50.0),
("spill_luma", 50.0),
],
_ => return None,
})
}
/// Extra parameters added to core effects by the 26.x rebuilds (old instances fall back to the
/// defaults, so projects round-trip unchanged).
pub(super) fn extend_core(v: &mut [EffectDef]) {
+17 -18
View File
@@ -161,17 +161,16 @@ pub fn ultra_key(img: &mut Image, e: &EffectInstance, cx: &FxCtx) {
let k = rgb_to_ycbcr(kc[0], kc[1], kc[2], Matrix::Bt709);
let kmag2 = (k[1] * k[1] + k[2] * k[2]).max(1e-6);
let setting = chv(e, "setting");
let (g_mul, ped_mul) = match setting {
1 => (0.85, 0.7),
2 => (1.2, 1.5),
_ => (1.0, 1.0),
let preset = filmcraft_project::effect::ultra_key_setting(setting);
let pf = |id: &str, fallback: f32| {
preset.and_then(|rows| rows.iter().find(|(k, _)| *k == id).map(|(_, v)| *v as f32)).unwrap_or(fallback)
};
let transparency = fv(e, "transparency", cx) / 100.0;
let highlight = fv(e, "highlight", cx) / 100.0;
let shadow = fv(e, "shadow", cx) / 100.0;
let tol = 0.08 + fv(e, "tolerance", cx) / 100.0 * 0.5;
let pedestal = (fv(e, "pedestal", cx) / 100.0 * 0.3 * ped_mul).min(0.9);
let gain = (1.0 + 2.0 * transparency) * g_mul;
let transparency = pf("transparency", fv(e, "transparency", cx)) / 100.0;
let highlight = pf("highlight", fv(e, "highlight", cx)) / 100.0;
let shadow = pf("shadow", fv(e, "shadow", cx)) / 100.0;
let tol = 0.08 + pf("tolerance", fv(e, "tolerance", cx)) / 100.0 * 0.5;
let pedestal = (pf("pedestal", fv(e, "pedestal", cx)) / 100.0 * 0.3).min(0.9);
let gain = 1.0 + 2.0 * transparency;
let output = chv(e, "output");
let dom = if kc[1] >= kc[0] && kc[1] >= kc[2] {
1
@@ -199,10 +198,10 @@ pub fn ultra_key(img: &mut Image, e: &EffectInstance, cx: &FxCtx) {
*a = ((m.clamp(0.0, 1.0) - pedestal) / (1.0 - pedestal)).clamp(0.0, 1.0);
});
// cleanup
let choke = fv(e, "choke", cx) / 100.0;
let soften = fv(e, "soften", cx) / 100.0;
let contrast = fv(e, "contrast", cx) / 100.0;
let mid = fv(e, "mid_point", cx) / 100.0;
let choke = pf("choke", fv(e, "choke", cx)) / 100.0;
let soften = pf("soften", fv(e, "soften", cx)) / 100.0;
let contrast = pf("contrast", fv(e, "contrast", cx)) / 100.0;
let mid = pf("mid_point", fv(e, "mid_point", cx)) / 100.0;
if choke > 0.0 {
let r = (choke * 4.0 * cx.px_scale).max(0.5);
let b = blur_plane(&alpha, img.w, img.h, r);
@@ -217,10 +216,10 @@ pub fn ultra_key(img: &mut Image, e: &EffectInstance, cx: &FxCtx) {
alpha.par_iter_mut().for_each(|a| *a = ((*a - mid) * g + mid).clamp(0.0, 1.0));
}
// spill suppression + colour correction on the foreground
let spill = fv(e, "spill", cx) / 100.0;
let desat = fv(e, "desaturate", cx) / 100.0;
let range = fv(e, "range", cx) / 100.0;
let sluma = fv(e, "spill_luma", cx) / 100.0;
let spill = pf("spill", fv(e, "spill", cx)) / 100.0;
let desat = pf("desaturate", fv(e, "desaturate", cx)) / 100.0;
let range = pf("range", fv(e, "range", cx)) / 100.0;
let sluma = pf("spill_luma", fv(e, "spill_luma", cx)) / 100.0;
let (cc_s, cc_h, cc_l) = (fv(e, "cc_saturation", cx) / 100.0, fv(e, "cc_hue", cx) / 360.0, fv(e, "cc_luminance", cx) / 100.0);
let cc = (cc_s - 1.0).abs() > 1e-4 || cc_h.abs() > 1e-6 || (cc_l - 1.0).abs() > 1e-4;
img.px.par_chunks_mut(4).zip(alpha.par_iter()).for_each(|(p, &a)| {
+6
View File
@@ -504,6 +504,12 @@ fn keyers_known_values() {
set(&mut e, "output", ParamValue::Choice(1));
let m = run("ultra_key", &e, &img, &cx());
assert!(m.get(1, 0)[0] < 0.05 && m.get(6, 0)[0] > 0.9 && m.get(1, 0)[3] == 1.0);
let mut aggressive = inst("ultra_key");
set(&mut aggressive, "setting", ParamValue::Choice(2));
let ag = run("ultra_key", &aggressive, &img, &cx());
let def = run("ultra_key", &inst("ultra_key"), &img, &cx());
let delta = (0..img.w * img.h).map(|i| (ag.px[i * 4 + 3] - def.px[i * 4 + 3]).abs()).fold(0.0f32, f32::max);
assert!(delta > 1e-4, "Aggressive Setting left the Default matte unchanged");
// Alpha Adjust: invert and opacity
let mut a = Image::filled(2, 2, [0.1, 0.1, 0.1, 0.25]);
let mut e = inst("alpha_adjust");