M9.3: Obsolete effects (Basic 3D, Gaussian Blur (Legacy)); move Luma Key, Spill Suppressor, Reduce Interlace Flicker to Obsolete
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2c4346a15e
commit
43ebf68fe0
|
|
@ -596,7 +596,15 @@ pub fn specs() -> Vec<EffectSpec> {
|
|||
],
|
||||
vector_blur,
|
||||
),
|
||||
spec("ec.blur.reduceflicker", "Reduce Interlace Flicker", vec![p("softness", "Softness", num(0.0), slider(0.0, 100.0, 0.0, 10.0, 2))], reduce_flicker),
|
||||
EffectSpec {
|
||||
category: "Obsolete",
|
||||
..spec(
|
||||
"ec.blur.reduceflicker",
|
||||
"Reduce Interlace Flicker",
|
||||
vec![p("softness", "Softness", num(0.0), slider(0.0, 100.0, 0.0, 10.0, 2))],
|
||||
reduce_flicker,
|
||||
)
|
||||
},
|
||||
spec(
|
||||
"ec.blur.cccross",
|
||||
"CC Cross Blur",
|
||||
|
|
|
|||
|
|
@ -485,7 +485,7 @@ pub fn specs() -> Vec<EffectSpec> {
|
|||
spec(
|
||||
"ec.key.luma",
|
||||
"Luma Key",
|
||||
"Keying",
|
||||
"Obsolete",
|
||||
vec![
|
||||
p("keyType", "Key Type", Value::Enum(1), popup(&["Key Out Brighter", "Key Out Darker", "Key Out Similar", "Key Out Dissimilar"])),
|
||||
p("threshold", "Threshold", num(0.0), b255()),
|
||||
|
|
@ -560,7 +560,7 @@ pub fn specs() -> Vec<EffectSpec> {
|
|||
spec(
|
||||
"ec.key.spill",
|
||||
"Spill Suppressor",
|
||||
"Keying",
|
||||
"Obsolete",
|
||||
vec![
|
||||
p("colorToSuppress", "Color To Suppress", col(0.0, 0.0, 1.0), ParamUi::Color),
|
||||
p("suppression", "Suppression", num(100.0), slider(0.0, 200.0, 0.0, 100.0, 0)),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,184 @@
|
|||
//! Obsolete-category effects.
|
||||
//! Obsolete-category effects: Basic 3D (tilt/swivel the layer in a simple perspective space
|
||||
//! with an optional specular highlight) and Gaussian Blur (Legacy).
|
||||
|
||||
use crate::EffectSpec;
|
||||
use effectcraft_keyframe::Value;
|
||||
use effectcraft_project::ParamUi;
|
||||
use effectcraft_raster::gaussian_blur;
|
||||
use rayon::prelude::*;
|
||||
|
||||
use crate::util::layer_rect;
|
||||
use crate::{Buf, EffectCtx, EffectSpec, num, p, popup, slider};
|
||||
|
||||
fn spec(id: &'static str, name: &'static str, params: Vec<crate::ParamSpec>, render: crate::RenderFn) -> EffectSpec {
|
||||
EffectSpec { id, name, category: "Obsolete", params, render, gpu: false, float: true }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Basic 3D
|
||||
|
||||
fn basic_3d(ctx: &EffectCtx, mut b: Buf) -> Buf {
|
||||
let swivel = ctx.params.f("swivel").to_radians();
|
||||
let tilt = ctx.params.f("tilt").to_radians();
|
||||
let dist = ctx.params.f("distanceToImage") * b.scale;
|
||||
let spec_on = ctx.params.b("specularHighlight");
|
||||
let wire = ctx.params.b("drawPreviewWireframe");
|
||||
if swivel == 0.0 && tilt == 0.0 && dist == 0.0 && !spec_on && !wire {
|
||||
return b;
|
||||
}
|
||||
let (lx, ly, lw, lh) = layer_rect(ctx, &b);
|
||||
let (cx, cy) = (lx + lw * 0.5, ly + lh * 0.5);
|
||||
// Eye on the -z axis at a focal distance comparable to a 50 mm lens.
|
||||
let f = lw.max(lh).max(1.0) * 1.4;
|
||||
// Plane basis after Y (swivel) then X (tilt) rotations.
|
||||
let (cs, ss) = (swivel.cos(), swivel.sin());
|
||||
let (ct, st) = (tilt.cos(), tilt.sin());
|
||||
let ux = [cs, 0.0, -ss];
|
||||
let uy = [ss * st, ct, cs * st];
|
||||
let n = [ss * ct, -st, cs * ct];
|
||||
let dot = |a: [f64; 3], b: [f64; 3]| a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
||||
let src = b.img.clone();
|
||||
let light = {
|
||||
let l = [-0.5f64, -0.6, -1.0];
|
||||
let k = 1.0 / dot(l, l).sqrt();
|
||||
l.map(|v| v * k)
|
||||
};
|
||||
b.img.rows_mut().for_each(|(y, row)| {
|
||||
for (x, px) in row.iter_mut().enumerate() {
|
||||
// Ray from the eye (0, 0, -f) through the pixel on the z = 0 plane (centre-relative).
|
||||
let d = [x as f64 + 0.5 - cx, y as f64 + 0.5 - cy, f];
|
||||
let o = [0.0, 0.0, -f];
|
||||
let denom = dot(d, n);
|
||||
if denom.abs() < 1e-9 {
|
||||
*px = [0.0; 4];
|
||||
continue;
|
||||
}
|
||||
let pc = [0.0, 0.0, dist];
|
||||
let t = dot([pc[0] - o[0], pc[1] - o[1], pc[2] - o[2]], n) / denom;
|
||||
if t <= 0.0 {
|
||||
*px = [0.0; 4];
|
||||
continue;
|
||||
}
|
||||
let hit = [o[0] + d[0] * t - pc[0], o[1] + d[1] * t - pc[1], o[2] + d[2] * t - pc[2]];
|
||||
let (u, v) = (dot(hit, ux), dot(hit, uy));
|
||||
let mut c = src.sample_bilinear(cx + u, cy + v);
|
||||
if spec_on && c[3] > 0.0 {
|
||||
// Reflect the view direction about the normal and compare with the light.
|
||||
let dl = dot(d, d).sqrt();
|
||||
let dv = d.map(|v| v / dl);
|
||||
let k = 2.0 * dot(dv, n);
|
||||
let r = [dv[0] - k * n[0], dv[1] - k * n[1], dv[2] - k * n[2]];
|
||||
let s = (-dot(r, light)).max(0.0).powi(40) as f32;
|
||||
let a = c[3];
|
||||
for ch in c.iter_mut().take(3) {
|
||||
*ch += s * a * (1.0 - *ch / a.max(1e-6)).max(0.0);
|
||||
}
|
||||
}
|
||||
if wire {
|
||||
let e = (u.abs() - lw * 0.5).abs().min((v.abs() - lh * 0.5).abs());
|
||||
if e < 0.75 && u.abs() <= lw * 0.5 + 0.75 && v.abs() <= lh * 0.5 + 0.75 {
|
||||
c = [1.0, 1.0, 1.0, 1.0];
|
||||
}
|
||||
}
|
||||
*px = c;
|
||||
}
|
||||
});
|
||||
b
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Gaussian Blur (Legacy)
|
||||
|
||||
fn gaussian_legacy(ctx: &EffectCtx, mut b: Buf) -> Buf {
|
||||
let s = ctx.params.f("blurriness").max(0.0) * 0.5 * b.scale;
|
||||
if s <= 0.0 {
|
||||
return b;
|
||||
}
|
||||
let (kx, ky) = match ctx.params.e("blurDimensions") {
|
||||
1 => (1.0, 0.0),
|
||||
2 => (0.0, 1.0),
|
||||
_ => (1.0, 1.0),
|
||||
};
|
||||
if !ctx.adjustment {
|
||||
b.pad((s * 3.0).ceil() as u32);
|
||||
}
|
||||
b.img = gaussian_blur(&b.img, s * kx, s * ky, ctx.adjustment);
|
||||
b
|
||||
}
|
||||
|
||||
pub fn specs() -> Vec<EffectSpec> {
|
||||
Vec::new()
|
||||
vec![
|
||||
spec(
|
||||
"ec.obsolete.basic3d",
|
||||
"Basic 3D",
|
||||
vec![
|
||||
p("swivel", "Swivel", num(0.0), ParamUi::Angle),
|
||||
p("tilt", "Tilt", num(0.0), ParamUi::Angle),
|
||||
p("distanceToImage", "Distance to Image", num(0.0), slider(-1000.0, 30000.0, -100.0, 100.0, 1)),
|
||||
p("specularHighlight", "Specular Highlight", Value::Bool(false), ParamUi::Checkbox),
|
||||
p("drawPreviewWireframe", "Draw Preview Wireframe", Value::Bool(false), ParamUi::Checkbox),
|
||||
],
|
||||
basic_3d,
|
||||
),
|
||||
spec(
|
||||
"ec.obsolete.gaussianlegacy",
|
||||
"Gaussian Blur (Legacy)",
|
||||
vec![
|
||||
p("blurriness", "Blurriness", num(0.0), slider(0.0, 1000.0, 0.0, 50.0, 1)),
|
||||
p("blurDimensions", "Blur Dimensions", Value::Enum(0), popup(&["Horizontal and Vertical", "Horizontal", "Vertical"])),
|
||||
],
|
||||
gaussian_legacy,
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{EffectEnv, run_fx};
|
||||
use effectcraft_raster::Image;
|
||||
|
||||
fn ramp() -> Image {
|
||||
let mut img = Image::new(20, 12);
|
||||
for y in 0..12 {
|
||||
for x in 0..20 {
|
||||
img.set(x, y, [x as f32 / 20.0, y as f32 / 12.0, 0.5, 1.0]);
|
||||
}
|
||||
}
|
||||
img
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_3d_zero_identity_swivel_narrows_and_is_deterministic() {
|
||||
let img = ramp();
|
||||
let id = run_fx("ec.obsolete.basic3d", &[], img.clone(), 0.0, EffectEnv::default());
|
||||
assert_eq!(id.img.data, img.data);
|
||||
let flat =
|
||||
run_fx("ec.obsolete.basic3d", &[("distanceToImage", num(0.0)), ("specularHighlight", Value::Bool(false))], img.clone(), 0.0, EffectEnv::default());
|
||||
assert_eq!(flat.img.data, img.data);
|
||||
let sw = run_fx("ec.obsolete.basic3d", &[("swivel", num(60.0))], img.clone(), 0.0, EffectEnv::default());
|
||||
assert_eq!(sw.img.get(0, 6)[3], 0.0, "edges uncovered after swivel");
|
||||
assert!(sw.img.get(10, 6)[3] > 0.99);
|
||||
let again = run_fx("ec.obsolete.basic3d", &[("swivel", num(60.0))], img.clone(), 0.0, EffectEnv::default());
|
||||
assert_eq!(sw.img.data, again.img.data);
|
||||
let far = run_fx("ec.obsolete.basic3d", &[("distanceToImage", num(20.0))], img, 0.0, EffectEnv::default());
|
||||
assert_eq!(far.img.get(0, 0)[3], 0.0, "farther image is smaller");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gaussian_legacy_zero_identity_and_blurs() {
|
||||
let mut img = Image::new(15, 15);
|
||||
img.set(7, 7, [1.0, 1.0, 1.0, 1.0]);
|
||||
let id = run_fx("ec.obsolete.gaussianlegacy", &[], img.clone(), 0.0, EffectEnv::default());
|
||||
assert_eq!(id.img.data, img.data);
|
||||
let out = run_fx("ec.obsolete.gaussianlegacy", &[("blurriness", num(4.0))], img.clone(), 0.0, EffectEnv::default());
|
||||
let total: f32 = out.img.data.iter().map(|p| p[3]).sum();
|
||||
assert!((total - 1.0).abs() < 1e-3, "{total}");
|
||||
let c = out.img.width / 2;
|
||||
assert!(out.img.get(c as i64, c as i64)[3] < 0.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moved_effects_are_obsolete() {
|
||||
for id in ["ec.key.luma", "ec.key.spill", "ec.blur.reduceflicker"] {
|
||||
assert_eq!(crate::find(id).unwrap().category, "Obsolete", "{id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue