M12.7: GPU colour effects (Color Balance, Vibrance, Lumetri, Black & White, Tritone, Colorama, Channel Mixer, Selective Color, Linear Color Key, Key Light)
fx_color kernels mirror the CPU effects operation for operation: one per-pixel pass for the point effects, Lumetri with its Sharpen luminance blur, and Key Light's matte pipeline (pre-blur, clip, rollback, despot, shrink/grow, softness, edge band) as plane passes. CPU fallback: Key Light with Inside/Outside masks or Hard Colour after Screen Softness, Colorama with Interpolate Palette off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
9a6389db86
commit
91ab3a3fc5
|
|
@ -641,6 +641,17 @@ pub const GPU_EFFECTS: &[&str] = &[
|
|||
"ec.color.exposure",
|
||||
"ec.channel.invert",
|
||||
"ec.distort.transform",
|
||||
// effectcraft-gpu fx_color
|
||||
"ec.color.colorbalance",
|
||||
"ec.color.vibrance",
|
||||
"ec.color.lumetri",
|
||||
"ec.color.blackwhite",
|
||||
"ec.color.tritone",
|
||||
"ec.color.colorama",
|
||||
"ec.color.channelmixer",
|
||||
"ec.color.selectivecolor",
|
||||
"ec.key.linearcolor",
|
||||
"ec.keying.keylight",
|
||||
];
|
||||
|
||||
/// Effects whose output depends on [`EffectCtx::time`] directly (not only through animated
|
||||
|
|
|
|||
|
|
@ -1,18 +1,396 @@
|
|||
//! GPU effects, colour family (kernels in `shaders/fx_color.wgsl`): the CPU effects' exact
|
||||
//! steps with the pixel loops as compute kernels.
|
||||
//!
|
||||
//! Colour Balance, Vibrance, Black & White, Tritone, Colorama, Channel Mixer, Selective Color
|
||||
//! and Linear Color Key are one per-pixel pass (`fxc_point`); Lumetri Color is a per-pixel pass
|
||||
//! plus, with Sharpen, a luminance plane blurred like `util::gauss_plane`; Key Light runs its
|
||||
//! matte pipeline (pre-blur, clip, rollback, despot, shrink / grow, softness, edge band) as
|
||||
//! plane passes. Key Light with an Inside / Outside mask, or Hard Colour replacement after
|
||||
//! Screen Softness, and Colorama with Interpolate Palette off run on the CPU.
|
||||
|
||||
use effectcraft_color::{luminance, rgb_to_hsl};
|
||||
use effectcraft_effects::EffectCtx;
|
||||
|
||||
use crate::context::Enc;
|
||||
use crate::effects::GBuf;
|
||||
use crate::context::{Enc, GpuImage, Params};
|
||||
use crate::effects::{GBuf, gaussian_blur};
|
||||
|
||||
/// Compute entry points in `fx_color.wgsl`.
|
||||
pub(crate) const KERNELS: &[&str] = &[];
|
||||
pub(crate) const KERNELS: &[&str] = &[
|
||||
"fxc_point",
|
||||
"fxc_lumetri",
|
||||
"fxc_luma",
|
||||
"fxc_sharpen",
|
||||
"fxc_morph",
|
||||
"fxc_lerp",
|
||||
"fxc_kl_raw",
|
||||
"fxc_kl_rollback",
|
||||
"fxc_kl_pack",
|
||||
"fxc_kl_band",
|
||||
"fxc_kl_edge",
|
||||
"fxc_kl_final",
|
||||
];
|
||||
|
||||
/// Effect ids implemented here.
|
||||
pub(crate) const IDS: &[&str] = &[];
|
||||
pub(crate) const IDS: &[&str] = &[
|
||||
"ec.color.colorbalance",
|
||||
"ec.color.vibrance",
|
||||
"ec.color.lumetri",
|
||||
"ec.color.blackwhite",
|
||||
"ec.color.tritone",
|
||||
"ec.color.colorama",
|
||||
"ec.color.channelmixer",
|
||||
"ec.color.selectivecolor",
|
||||
"ec.key.linearcolor",
|
||||
"ec.keying.keylight",
|
||||
];
|
||||
|
||||
/// Run effect `id` (one of [`IDS`]); `None` = this parameter combination runs on the CPU.
|
||||
pub(crate) fn apply(_e: &mut Enc, _id: &str, _ctx: &EffectCtx, _b: GBuf) -> Option<GBuf> {
|
||||
None
|
||||
pub(crate) fn apply(e: &mut Enc, id: &str, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
match id {
|
||||
"ec.color.lumetri" => lumetri(e, ctx, b),
|
||||
"ec.keying.keylight" => key_light(e, ctx, b),
|
||||
_ => point(e, id, ctx, b),
|
||||
}
|
||||
}
|
||||
|
||||
fn rgb(c: [f32; 4]) -> [f32; 4] {
|
||||
[c[0], c[1], c[2], 0.0]
|
||||
}
|
||||
|
||||
/// Same selective colour ids as `color2::SC_IDS`.
|
||||
const SC_IDS: [[&str; 4]; 9] = [
|
||||
["redsCyan", "redsMagenta", "redsYellow", "redsBlack"],
|
||||
["yellowsCyan", "yellowsMagenta", "yellowsYellow", "yellowsBlack"],
|
||||
["greensCyan", "greensMagenta", "greensYellow", "greensBlack"],
|
||||
["cyansCyan", "cyansMagenta", "cyansYellow", "cyansBlack"],
|
||||
["bluesCyan", "bluesMagenta", "bluesYellow", "bluesBlack"],
|
||||
["magentasCyan", "magentasMagenta", "magentasYellow", "magentasBlack"],
|
||||
["whitesCyan", "whitesMagenta", "whitesYellow", "whitesBlack"],
|
||||
["neutralsCyan", "neutralsMagenta", "neutralsYellow", "neutralsBlack"],
|
||||
["blacksCyan", "blacksMagenta", "blacksYellow", "blacksBlack"],
|
||||
];
|
||||
|
||||
fn point(e: &mut Enc, id: &str, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let pr = ctx.params;
|
||||
let f = |k: &str| pr.f(k) as f32;
|
||||
let mut p = Params::default();
|
||||
let mut data = None;
|
||||
match id {
|
||||
"ec.color.colorbalance" => {
|
||||
let g = |k: &str| f(k) / 100.0;
|
||||
p.u[0] = [1, pr.b("preserveLuminosity") as u32, 0, 0];
|
||||
p.f[0] = [g("shadowRed"), g("shadowGreen"), g("shadowBlue"), 0.0];
|
||||
p.f[1] = [g("midRed"), g("midGreen"), g("midBlue"), 0.0];
|
||||
p.f[2] = [g("hiRed"), g("hiGreen"), g("hiBlue"), 0.0];
|
||||
}
|
||||
"ec.color.vibrance" => {
|
||||
p.u[0][0] = 2;
|
||||
p.f[0] = [f("vibrance") / 100.0, f("saturation") / 100.0, 0.0, 0.0];
|
||||
}
|
||||
"ec.color.blackwhite" => {
|
||||
let w = ["reds", "yellows", "greens", "cyans", "blues", "magentas"].map(|k| f(k) / 100.0);
|
||||
let tc = pr.color("tintColor");
|
||||
let (th, ts, _) = rgb_to_hsl(tc[0], tc[1], tc[2]);
|
||||
p.u[0] = [3, pr.b("tint") as u32, 0, 0];
|
||||
p.f[0] = [w[0], w[1], w[2], w[3]];
|
||||
p.f[1] = [w[4], w[5], 0.0, 0.0];
|
||||
p.f[2] = [th, ts, 0.0, 0.0];
|
||||
}
|
||||
"ec.color.tritone" => {
|
||||
p.u[0][0] = 4;
|
||||
p.f[0] = rgb(pr.color("highlights"));
|
||||
p.f[1] = rgb(pr.color("midtones"));
|
||||
p.f[2] = rgb(pr.color("shadows"));
|
||||
p.f[3][0] = 1.0 - f("blend") / 100.0;
|
||||
}
|
||||
"ec.color.colorama" => {
|
||||
let smooth = pr.get("outputCycle/interpolatePalette").is_none_or(|v| v.as_bool());
|
||||
// The posterized palette is discontinuous: phases on a step (common with 8 bpc
|
||||
// input) can round to the other side in the GPU's float maths, so it runs on the CPU.
|
||||
if !smooth {
|
||||
return None;
|
||||
}
|
||||
p.u[0] = [5, pr.e("inputPhase/mode"), smooth as u32, 0];
|
||||
p.f[0] = [pr.f("outputCycle/cycles").max(0.0) as f32, f("inputPhase/phaseShift") / 360.0, 1.0 - f("blend") / 100.0, 0.0];
|
||||
}
|
||||
"ec.color.channelmixer" => {
|
||||
let g = |k: &str| f(k) / 100.0;
|
||||
p.u[0] = [6, pr.b("monochrome") as u32, 0, 0];
|
||||
p.f[0] = [g("rr"), g("rg"), g("rb"), g("rc")];
|
||||
p.f[1] = [g("gr"), g("gg"), g("gb"), g("gc")];
|
||||
p.f[2] = [g("br"), g("bg"), g("bb"), g("bc")];
|
||||
}
|
||||
"ec.color.selectivecolor" => {
|
||||
let adj: Vec<f32> = SC_IDS.iter().flat_map(|ids| ids.map(|k| f(k) / 100.0)).collect();
|
||||
if adj.iter().all(|v| *v == 0.0) {
|
||||
return Some(b);
|
||||
}
|
||||
p.u[0] = [7, (pr.e("method") == 0) as u32, 0, 0];
|
||||
data = Some(e.data(&adj));
|
||||
}
|
||||
"ec.key.linearcolor" => {
|
||||
let view = pr.e("view");
|
||||
if view == 1 {
|
||||
return Some(b);
|
||||
}
|
||||
let k = pr.color("keyColor");
|
||||
let s = k[0] + k[1] + k[2];
|
||||
let kc = if s <= 1e-6 { [1.0 / 3.0, 1.0 / 3.0] } else { [k[0] / s, k[1] / s] };
|
||||
let (kh, _, _) = rgb_to_hsl(k[0], k[1], k[2]);
|
||||
p.u[0] = [8, pr.e("matchColors"), (pr.e("keyOperation") == 1) as u32, view];
|
||||
p.f[0] = [k[0], k[1], k[2], f("tolerance") / 100.0];
|
||||
p.f[1] = [f("softness") / 100.0, kc[0], kc[1], kh];
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
let out = e.image(b.img.width, b.img.height);
|
||||
e.pixels("fxc_point", &p, &b.img, None, &out, data.as_ref());
|
||||
Some(GBuf { img: out, ..b })
|
||||
}
|
||||
|
||||
/// color3::tint_offset.
|
||||
fn tint_offset(c: [f32; 4]) -> [f32; 3] {
|
||||
let l = (c[0] + c[1] + c[2]) / 3.0;
|
||||
[c[0] - l, c[1] - l, c[2] - l]
|
||||
}
|
||||
|
||||
fn lumetri(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let pr = ctx.params;
|
||||
let f = |k: &str| (pr.f(k) / 100.0) as f32;
|
||||
let mut d: Vec<f32> = vec![
|
||||
f("basicCorrection/whiteBalance/temperature"),
|
||||
f("basicCorrection/whiteBalance/tint"),
|
||||
2f32.powf(pr.f("basicCorrection/tone/exposure") as f32),
|
||||
f("basicCorrection/tone/contrast"),
|
||||
f("basicCorrection/tone/highlights"),
|
||||
f("basicCorrection/tone/shadows"),
|
||||
f("basicCorrection/tone/whites"),
|
||||
f("basicCorrection/tone/blacks"),
|
||||
f("basicCorrection/saturation"),
|
||||
f("creative/lookIntensity"),
|
||||
f("creative/adjustments/fadedFilm"),
|
||||
f("creative/adjustments/vibrance"),
|
||||
f("creative/adjustments/creativeSaturation"),
|
||||
(0.5 + 0.25 * f("creative/adjustments/tintBalance")).clamp(0.05, 0.95),
|
||||
];
|
||||
d.extend(tint_offset(pr.color("creative/adjustments/shadowTint")));
|
||||
d.extend(tint_offset(pr.color("creative/adjustments/highlightTint")));
|
||||
for c in ["Master", "Red", "Green", "Blue"] {
|
||||
for part in ["Shadows", "Midtones", "Highlights"] {
|
||||
d.push(f(&format!("curves/curve{c}{part}")));
|
||||
}
|
||||
}
|
||||
for w in ["shadowsWheel", "midtonesWheel", "highlightsWheel"] {
|
||||
d.extend(tint_offset(pr.color(&format!("colorWheels/{w}"))));
|
||||
}
|
||||
let (lx, ly) = (b.offset[0], b.offset[1]);
|
||||
let (lw, lh) = (ctx.layer_size[0] * b.scale, ctx.layer_size[1] * b.scale);
|
||||
d.extend([
|
||||
pr.f("vignette/vignetteAmount") as f32,
|
||||
f("vignette/vignetteMidpoint") * 0.9,
|
||||
f("vignette/vignetteRoundness"),
|
||||
f("vignette/vignetteFeather"),
|
||||
(lx + lw * 0.5) as f32,
|
||||
(ly + lh * 0.5) as f32,
|
||||
lw as f32,
|
||||
lh as f32,
|
||||
(lw / lh.max(1e-6)) as f32,
|
||||
]);
|
||||
debug_assert_eq!(d.len(), 50);
|
||||
let buf = e.data(&d);
|
||||
let out = e.image(b.img.width, b.img.height);
|
||||
e.pixels("fxc_lumetri", &Params::default(), &b.img, None, &out, Some(&buf));
|
||||
let sharpen = f("creative/adjustments/sharpen");
|
||||
if sharpen == 0.0 {
|
||||
return Some(GBuf { img: out, ..b });
|
||||
}
|
||||
let luma = e.image(out.width, out.height);
|
||||
e.pixels("fxc_luma", &Params::default(), &out, None, &luma, None);
|
||||
let blurred = gaussian_blur(e, &luma, b.scale, b.scale, true);
|
||||
let mut p = Params::default();
|
||||
p.f[0][0] = sharpen * 2.0;
|
||||
let sharp = e.image(out.width, out.height);
|
||||
e.pixels("fxc_sharpen", &p, &out, Some(&blurred), &sharp, None);
|
||||
Some(GBuf { img: sharp, ..b })
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- planes
|
||||
|
||||
/// util::morph_plane with the same radius on both axes; `modes` = per-channel operation
|
||||
/// (0 erode, 1 dilate, 2 keep).
|
||||
fn morph(e: &mut Enc, img: &GpuImage, r: u32, modes: [u32; 4]) -> GpuImage {
|
||||
if r == 0 {
|
||||
return img.clone();
|
||||
}
|
||||
let mut cur = img.clone();
|
||||
for vertical in [0, 1] {
|
||||
let mut p = Params::default();
|
||||
p.u[0] = [r, vertical, 0, 0];
|
||||
p.u[1] = modes;
|
||||
let out = e.image(cur.width, cur.height);
|
||||
e.pixels("fxc_morph", &p, &cur, None, &out, None);
|
||||
cur = out;
|
||||
}
|
||||
cur
|
||||
}
|
||||
|
||||
/// util::morph_frac (blend between the two nearest integer radii).
|
||||
fn morph_frac(e: &mut Enc, img: &GpuImage, radius: f64, modes: [u32; 4]) -> GpuImage {
|
||||
if radius <= 0.0 {
|
||||
return img.clone();
|
||||
}
|
||||
let r0 = radius.floor() as u32;
|
||||
let t = (radius - r0 as f64) as f32;
|
||||
let a = morph(e, img, r0, modes);
|
||||
if t < 1e-4 {
|
||||
return a;
|
||||
}
|
||||
let b = morph(e, img, r0 + 1, modes);
|
||||
let mut p = Params::default();
|
||||
p.f[0][0] = t;
|
||||
let out = e.image(img.width, img.height);
|
||||
e.pixels("fxc_lerp", &p, &a, Some(&b), &out, None);
|
||||
out
|
||||
}
|
||||
|
||||
const ERODE_X: [u32; 4] = [0, 2, 2, 2];
|
||||
const DILATE_X: [u32; 4] = [1, 2, 2, 2];
|
||||
|
||||
// ---------------------------------------------------------------- Key Light
|
||||
|
||||
/// keylight::primary.
|
||||
fn primary(s: [f32; 3]) -> [u32; 3] {
|
||||
let pi: usize = if s[1] >= s[0] && s[1] >= s[2] {
|
||||
1
|
||||
} else if s[2] >= s[0] {
|
||||
2
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let (a, b) = match pi {
|
||||
0 => (1, 2),
|
||||
1 => (0, 2),
|
||||
_ => (0, 1),
|
||||
};
|
||||
let ix = if s[a] >= s[b] { [pi, a, b] } else { [pi, b, a] };
|
||||
ix.map(|i| i as u32)
|
||||
}
|
||||
|
||||
fn neutralise(c: [f32; 3], bias: [f32; 3]) -> [f32; 3] {
|
||||
let l = luminance(bias[0], bias[1], bias[2]).max(1e-4);
|
||||
[0, 1, 2].map(|i| c[i] * l / bias[i].max(1e-4))
|
||||
}
|
||||
|
||||
fn key_light(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let pr = ctx.params;
|
||||
let view = pr.e("view");
|
||||
if view == 0 {
|
||||
return Some(b);
|
||||
}
|
||||
// Inside / Outside masks (polygon coverage) run on the CPU.
|
||||
let has_mask = |id: &str| pr.e(id).checked_sub(1).is_some_and(|i| ctx.env.masks.get(i as usize).is_some());
|
||||
if has_mask("insideMask/insideMask") || has_mask("outsideMask/outsideMask") {
|
||||
return None;
|
||||
}
|
||||
let sc = pr.color("screenColour");
|
||||
let screen = [sc[0], sc[1], sc[2]];
|
||||
let ix = primary(screen);
|
||||
let (pi, o1, o2) = (ix[0] as usize, ix[1] as usize, ix[2] as usize);
|
||||
let gain = pr.f("screenGain") as f32 / 100.0;
|
||||
let bal = pr.f("screenBalance") as f32 / 100.0;
|
||||
let dbias = pr.color("despillBias");
|
||||
let abias = if pr.b("lockBiasesTogether") { dbias } else { pr.color("alphaBias") };
|
||||
let (dbias, abias) = ([dbias[0], dbias[1], dbias[2]], [abias[0], abias[1], abias[2]]);
|
||||
let s_alpha = neutralise(screen, abias);
|
||||
let sd = (s_alpha[pi] - (bal * s_alpha[o1] + (1.0 - bal) * s_alpha[o2])).max(1e-3);
|
||||
let cb = pr.f("screenMatte/clipBlack") as f32 / 100.0;
|
||||
let cw = pr.f("screenMatte/clipWhite") as f32 / 100.0;
|
||||
let rollback = pr.f("screenMatte/clipRollback") * b.scale;
|
||||
let preblur = pr.f("screenPreblur") * b.scale;
|
||||
let sg = pr.f("screenMatte/screenShrinkGrow") * b.scale;
|
||||
let soft = pr.f("screenMatte/screenSoftness") * b.scale;
|
||||
let despot_b = pr.f("screenMatte/screenDespotBlack") * b.scale;
|
||||
let despot_w = pr.f("screenMatte/screenDespotWhite") * b.scale;
|
||||
// Hard Colour replaces wherever the screen matte exceeds the raw matte by any amount; after
|
||||
// Screen Softness's blur that compares float-rounded sums, so it runs on the CPU.
|
||||
if pr.e("screenMatte/replaceMethod") == 2 && soft > 0.0 {
|
||||
return None;
|
||||
}
|
||||
let (w, h) = (b.img.width, b.img.height);
|
||||
|
||||
let key_src = if preblur > 0.0 { gaussian_blur(e, &b.img, preblur * 0.5, preblur * 0.5, true) } else { b.img.clone() };
|
||||
let mut p = Params::default();
|
||||
p.u[0] = [ix[0], ix[1], ix[2], 0];
|
||||
p.f[0] = [abias[0], abias[1], abias[2], sd];
|
||||
p.f[1] = [gain, bal, cb, cw];
|
||||
let raw = e.image(w, h);
|
||||
e.pixels("fxc_kl_raw", &p, &key_src, None, &raw, None);
|
||||
let mut m = raw.clone();
|
||||
if rollback > 0.0 {
|
||||
let sg_img = morph_frac(e, &m, rollback, [0, 1, 2, 2]);
|
||||
let out = e.image(w, h);
|
||||
e.pixels("fxc_kl_rollback", &Params::default(), &sg_img, Some(&raw), &out, None);
|
||||
m = out;
|
||||
}
|
||||
if despot_b > 0.0 {
|
||||
let d = morph_frac(e, &m, despot_b, DILATE_X);
|
||||
m = morph_frac(e, &d, despot_b, ERODE_X);
|
||||
}
|
||||
if despot_w > 0.0 {
|
||||
let d = morph_frac(e, &m, despot_w, ERODE_X);
|
||||
m = morph_frac(e, &d, despot_w, DILATE_X);
|
||||
}
|
||||
if sg < 0.0 {
|
||||
m = morph_frac(e, &m, -sg, ERODE_X);
|
||||
} else if sg > 0.0 {
|
||||
m = morph_frac(e, &m, sg, DILATE_X);
|
||||
}
|
||||
if soft > 0.0 {
|
||||
m = gaussian_blur(e, &m, soft * 0.5, soft * 0.5, true);
|
||||
}
|
||||
let mut planes = e.image(w, h);
|
||||
e.pixels("fxc_kl_pack", &Params::default(), &m, Some(&raw), &planes, None);
|
||||
let src_alpha_mode = pr.e("insideMask/sourceAlpha");
|
||||
if pr.b("edgeColourCorrection/enableEdgeColourCorrection") {
|
||||
let grow = pr.f("edgeColourCorrection/edgeGrow") * b.scale;
|
||||
let hard = (pr.f("edgeColourCorrection/edgeHardness") / 100.0) as f32;
|
||||
let esoft = pr.f("edgeColourCorrection/edgeSoftness") * b.scale;
|
||||
let mut p = Params::default();
|
||||
p.u[0][0] = src_alpha_mode;
|
||||
let mut band = e.image(w, h);
|
||||
e.pixels("fxc_kl_band", &p, &b.img, Some(&planes), &band, None);
|
||||
if grow > 0.0 {
|
||||
band = morph_frac(e, &band, grow, DILATE_X);
|
||||
}
|
||||
if esoft > 0.0 {
|
||||
band = gaussian_blur(e, &band, esoft * 0.5, esoft * 0.5, true);
|
||||
}
|
||||
let mut p = Params::default();
|
||||
p.f[0][0] = hard;
|
||||
let out = e.image(w, h);
|
||||
e.pixels("fxc_kl_edge", &p, &planes, Some(&band), &out, None);
|
||||
planes = out;
|
||||
}
|
||||
let pc = |k: &str| pr.f(k) as f32 / 100.0;
|
||||
let l0 = (luminance(dbias[0], dbias[1], dbias[2]) / dbias[pi].max(1e-4)).max(1e-4);
|
||||
let mut p = Params::default();
|
||||
p.u[0] = [ix[0], ix[1], ix[2], view];
|
||||
p.u[1] = [
|
||||
pr.e("screenMatte/replaceMethod"),
|
||||
pr.b("foregroundColourCorrection/enableColourCorrection") as u32,
|
||||
pr.b("unpremultiplyResult") as u32,
|
||||
src_alpha_mode,
|
||||
];
|
||||
p.f[0] = [screen[0], screen[1], screen[2], bal];
|
||||
p.f[1] = [dbias[0], dbias[1], dbias[2], l0];
|
||||
p.f[2] = pr.color("screenMatte/replaceColour");
|
||||
p.f[3] =
|
||||
[pc("foregroundColourCorrection/saturation"), pc("foregroundColourCorrection/contrast") + 1.0, pc("foregroundColourCorrection/brightness") + 1.0, 0.0];
|
||||
p.f[4] = [pc("edgeColourCorrection/edgeSaturation"), pc("edgeColourCorrection/edgeContrast") + 1.0, pc("edgeColourCorrection/edgeBrightness") + 1.0, 0.0];
|
||||
p.f[5] = ["sourceCrops/cropLeft", "sourceCrops/cropRight", "sourceCrops/cropTop", "sourceCrops/cropBottom"].map(|k| (pr.f(k) / 100.0) as f32);
|
||||
p.f[6] = [b.offset[0] as f32, b.offset[1] as f32, b.scale as f32, 0.0];
|
||||
p.f[7] = [ctx.layer_size[0].max(1e-9) as f32, ctx.layer_size[1].max(1e-9) as f32, 0.0, 0.0];
|
||||
let out = e.image(w, h);
|
||||
e.pixels("fxc_kl_final", &p, &b.img, Some(&planes), &out, None);
|
||||
Some(GBuf { img: out, ..b })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,3 +149,5 @@ mod tests_3d;
|
|||
mod tests_adjust;
|
||||
#[cfg(test)]
|
||||
mod tests_adv3d;
|
||||
#[cfg(test)]
|
||||
mod tests_fx_color;
|
||||
|
|
|
|||
|
|
@ -1,2 +1,707 @@
|
|||
// GPU effects (color family): see src/fx_color.rs. Each entry point mirrors the CPU effect in
|
||||
// effectcraft-effects operation for operation; parameter layouts are documented per entry.
|
||||
// Every name here is prefixed `fxc_` (the family files share one module).
|
||||
|
||||
// ---------------------------------------------------------------- helpers
|
||||
|
||||
// util::unpremul: straight colour (0 when alpha ≤ 1e-6).
|
||||
fn fxc_unpremul(px: vec4<f32>) -> vec3<f32> {
|
||||
if (px.w > 1e-6) {
|
||||
return px.xyz / px.w;
|
||||
}
|
||||
return vec3<f32>(0.0);
|
||||
}
|
||||
|
||||
// util::smoothstep.
|
||||
fn fxc_smoothstep(e0: f32, e1: f32, x: f32) -> f32 {
|
||||
if (abs(e1 - e0) < 1e-9) {
|
||||
return select(1.0, 0.0, x < e0);
|
||||
}
|
||||
let t = clamp((x - e0) / (e1 - e0), 0.0, 1.0);
|
||||
return t * t * (3.0 - 2.0 * t);
|
||||
}
|
||||
|
||||
fn fxc_mix(a: vec3<f32>, b: vec3<f32>, t: f32) -> vec3<f32> {
|
||||
return a + (b - a) * t;
|
||||
}
|
||||
|
||||
// color::hsv_to_rgb.
|
||||
fn fxc_hsv_to_rgb(h: f32, s: f32, v: f32) -> vec3<f32> {
|
||||
let h6 = fract_euclid(h) * 6.0;
|
||||
let i = floor(h6);
|
||||
let f = h6 - i;
|
||||
let p = v * (1.0 - s);
|
||||
let q = v * (1.0 - s * f);
|
||||
let t = v * (1.0 - s * (1.0 - f));
|
||||
switch i32(i) {
|
||||
case 0: { return vec3<f32>(v, t, p); }
|
||||
case 1: { return vec3<f32>(q, v, p); }
|
||||
case 2: { return vec3<f32>(p, v, t); }
|
||||
case 3: { return vec3<f32>(p, q, v); }
|
||||
case 4: { return vec3<f32>(t, p, v); }
|
||||
default: { return vec3<f32>(v, p, q); }
|
||||
}
|
||||
}
|
||||
|
||||
fn fxc_pixel(gid: vec3<u32>) -> vec2<i32> {
|
||||
let dims = out_dims();
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (p.x >= dims.x || p.y >= dims.y) {
|
||||
return vec2<i32>(-1);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- per-pixel colour effects
|
||||
|
||||
// Colorama's input phase (color_fx::colorama_phase).
|
||||
fn fxc_colorama_phase(c: vec3<f32>, a: f32, mode: u32) -> f32 {
|
||||
var v: f32;
|
||||
switch mode {
|
||||
case 1u: { v = c.x; }
|
||||
case 2u: { v = c.y; }
|
||||
case 3u: { v = c.z; }
|
||||
case 4u: { v = rgb_to_hsl(c).x; }
|
||||
case 5u: { v = rgb_to_hsl(c).z; }
|
||||
case 6u: { v = rgb_to_hsl(c).y; }
|
||||
case 7u: { v = max(max(c.x, c.y), c.z); }
|
||||
case 8u: { v = a; }
|
||||
case 9u: { v = 0.0; }
|
||||
default: { v = luminance(c); }
|
||||
}
|
||||
return clamp(v, 0.0, 1.0);
|
||||
}
|
||||
|
||||
// Selective Color range weights (color2::sc_weights).
|
||||
fn fxc_sc_weights(c0: vec3<f32>) -> array<f32, 9> {
|
||||
let c = clamp(c0, vec3<f32>(0.0), vec3<f32>(1.0));
|
||||
// Stable descending sort of the channel indices.
|
||||
var idx = vec3<u32>(0u, 1u, 2u);
|
||||
for (var i = 1u; i < 3u; i++) {
|
||||
var j = i;
|
||||
while (j > 0u && c[idx[j]] > c[idx[j - 1u]]) {
|
||||
let t = idx[j];
|
||||
idx[j] = idx[j - 1u];
|
||||
idx[j - 1u] = t;
|
||||
j--;
|
||||
}
|
||||
}
|
||||
let mx = c[idx.x];
|
||||
let md = c[idx.y];
|
||||
let mn = c[idx.z];
|
||||
var w = array<f32, 9>(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
|
||||
var prim = array<u32, 3>(0u, 2u, 4u);
|
||||
var sec = array<u32, 3>(3u, 5u, 1u);
|
||||
w[prim[idx.x]] = mx - md;
|
||||
w[sec[idx.z]] = md - mn;
|
||||
w[6] = max((mn - 0.5) * 2.0, 0.0);
|
||||
w[8] = max((0.5 - mx) * 2.0, 0.0);
|
||||
w[7] = clamp(1.0 - (abs(mx - 0.5) + abs(mn - 0.5)), 0.0, 1.0);
|
||||
return w;
|
||||
}
|
||||
|
||||
// u[0].x = op:
|
||||
// 1 Color Balance f[0] = shadows, f[1] = midtones, f[2] = highlights; u[0].y = preserve luminosity
|
||||
// 2 Vibrance f[0] = (vibrance, saturation)
|
||||
// 3 Black & White f[0] = (reds, yellows, greens, cyans), f[1] = (blues, magentas);
|
||||
// u[0].y = tint; f[2] = (tint hue, tint saturation)
|
||||
// 4 Tritone f[0] = highlights, f[1] = midtones, f[2] = shadows; f[3].x = 1 − blend
|
||||
// 5 Colorama u[0].y = phase mode, u[0].z = interpolate; f[0] = (cycles, shift, 1 − blend)
|
||||
// 6 Channel Mixer f[0..3] = output rows (r, g, b, const); u[0].y = monochrome
|
||||
// 7 Selective Color data = 9 × (cyan, magenta, yellow, black); u[0].y = relative
|
||||
// 8 Linear Color Key f[0] = (key rgb, tolerance), f[1] = (softness, key chroma xy, key hue);
|
||||
// u[0] = (_, match mode, keep colours, view)
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxc_point(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = fxc_pixel(gid);
|
||||
if (p.x < 0) {
|
||||
return;
|
||||
}
|
||||
var px = textureLoad(src, p, 0);
|
||||
let a = px.w;
|
||||
switch P.u[0].x {
|
||||
case 1u: {
|
||||
if (a > 0.0) {
|
||||
let c = px.xyz / a;
|
||||
let l = clamp(luminance(c), 0.0, 1.0);
|
||||
let ws = clamp(1.0 - l * 2.0, 0.0, 1.0);
|
||||
let wh = clamp(l * 2.0 - 1.0, 0.0, 1.0);
|
||||
let wm = 1.0 - ws - wh;
|
||||
var o = max(c + (P.f[0].xyz * ws + P.f[1].xyz * wm + P.f[2].xyz * wh) * 0.5, vec3<f32>(0.0));
|
||||
if (P.u[0].y != 0u) {
|
||||
let d = luminance(c) - luminance(o);
|
||||
o = max(o + d, vec3<f32>(0.0));
|
||||
}
|
||||
px = vec4<f32>(o * a, a);
|
||||
}
|
||||
}
|
||||
case 2u: {
|
||||
if (a > 0.0) {
|
||||
let c = px.xyz / a;
|
||||
let hsl = rgb_to_hsl(c);
|
||||
let vib = P.f[0].x;
|
||||
let sat = P.f[0].y;
|
||||
let s = clamp(hsl.y * (1.0 + sat) + vib * (1.0 - hsl.y) * min(hsl.y, 0.5), 0.0, 1.0);
|
||||
px = vec4<f32>(hsl_to_rgb(hsl.x, s, hsl.z) * a, a);
|
||||
}
|
||||
}
|
||||
case 3u: {
|
||||
if (a > 0.0) {
|
||||
let c = px.xyz / a;
|
||||
var w = array<f32, 6>(P.f[0].x, P.f[0].y, P.f[0].z, P.f[0].w, P.f[1].x, P.f[1].y);
|
||||
let hsl = rgb_to_hsl(c);
|
||||
let hp = hsl.x * 6.0;
|
||||
let i = u32(max(floor(hp), 0.0)) % 6u;
|
||||
let f = hp - floor(hp);
|
||||
let wt = w[i] + (w[(i + 1u) % 6u] - w[i]) * f;
|
||||
let base = luminance(c);
|
||||
let gray = max(base + (wt - 0.5) * hsl.y * 0.6, 0.0);
|
||||
var o = vec3<f32>(gray);
|
||||
if (P.u[0].y != 0u) {
|
||||
o = hsl_to_rgb(P.f[2].x, P.f[2].y, min(gray, 1.0));
|
||||
}
|
||||
px = vec4<f32>(o * a, a);
|
||||
}
|
||||
}
|
||||
case 4u: {
|
||||
if (a > 0.0) {
|
||||
let c = px.xyz / a;
|
||||
let l = clamp(luminance(c), 0.0, 1.0);
|
||||
var t: vec3<f32>;
|
||||
if (l < 0.5) {
|
||||
t = fxc_mix(P.f[2].xyz, P.f[1].xyz, l * 2.0);
|
||||
} else {
|
||||
t = fxc_mix(P.f[1].xyz, P.f[0].xyz, (l - 0.5) * 2.0);
|
||||
}
|
||||
px = vec4<f32>(fxc_mix(c, t, P.f[3].x) * a, a);
|
||||
}
|
||||
}
|
||||
case 5u: {
|
||||
if (a > 0.0) {
|
||||
let c = px.xyz / a;
|
||||
var ph = fract_euclid(fxc_colorama_phase(c, a, P.u[0].y) * P.f[0].x + P.f[0].y);
|
||||
if (P.u[0].z == 0u) {
|
||||
ph = floor(ph * 6.0) / 6.0;
|
||||
}
|
||||
let o = fxc_mix(c, fxc_hsv_to_rgb(ph, 1.0, 1.0), P.f[0].z);
|
||||
px = vec4<f32>(o * a, a);
|
||||
}
|
||||
}
|
||||
case 6u: {
|
||||
if (a > 0.0) {
|
||||
let c = px.xyz / a;
|
||||
var o: vec3<f32>;
|
||||
for (var i = 0; i < 3; i++) {
|
||||
let r = P.f[i];
|
||||
o[i] = max(c.x * r.x + c.y * r.y + c.z * r.z + r.w, 0.0);
|
||||
}
|
||||
if (P.u[0].y != 0u) {
|
||||
o = vec3<f32>(o.x);
|
||||
}
|
||||
px = vec4<f32>(o * a, a);
|
||||
}
|
||||
}
|
||||
case 7u: {
|
||||
// color2::map_ca: fully transparent black stays.
|
||||
if (a <= 0.0 && px.x == 0.0 && px.y == 0.0 && px.z == 0.0) {
|
||||
textureStore(out, p, px);
|
||||
return;
|
||||
}
|
||||
let c = fxc_unpremul(px);
|
||||
var w = fxc_sc_weights(c);
|
||||
var o = c;
|
||||
let relative = P.u[0].y != 0u;
|
||||
for (var r = 0u; r < 9u; r++) {
|
||||
let wr = w[r];
|
||||
if (wr <= 0.0) {
|
||||
continue;
|
||||
}
|
||||
let k = data[r * 4u + 3u];
|
||||
for (var i = 0u; i < 3u; i++) {
|
||||
let ink = 1.0 - c[i];
|
||||
var delta = data[r * 4u + i] + k;
|
||||
if (relative) {
|
||||
delta = delta * max(ink, 0.0);
|
||||
}
|
||||
o[i] -= wr * delta;
|
||||
}
|
||||
}
|
||||
let na = clamp(max(a, 0.0), 0.0, 1.0);
|
||||
px = vec4<f32>(max(o, vec3<f32>(0.0)) * na, na);
|
||||
}
|
||||
case 8u: {
|
||||
let c = fxc_unpremul(px);
|
||||
let key = P.f[0].xyz;
|
||||
let tol = P.f[0].w;
|
||||
let soft = P.f[1].x;
|
||||
var d: f32;
|
||||
switch P.u[0].y {
|
||||
case 1u: {
|
||||
let hsl = rgb_to_hsl(c);
|
||||
if (hsl.y < 0.02) {
|
||||
d = 1.0;
|
||||
} else {
|
||||
let dh = abs(hsl.x - P.f[1].w);
|
||||
d = min(dh, 1.0 - dh) * 2.0;
|
||||
}
|
||||
}
|
||||
case 2u: {
|
||||
let s = c.x + c.y + c.z;
|
||||
var pc = vec2<f32>(1.0 / 3.0, 1.0 / 3.0);
|
||||
if (s > 1e-6) {
|
||||
pc = vec2<f32>(c.x / s, c.y / s);
|
||||
}
|
||||
let dx = pc.x - P.f[1].y;
|
||||
let dy = pc.y - P.f[1].z;
|
||||
d = sqrt(dx * dx + dy * dy) / 1.4142135;
|
||||
}
|
||||
default: {
|
||||
let e = c - key;
|
||||
d = sqrt(e.x * e.x + e.y * e.y + e.z * e.z) / 1.7320508;
|
||||
}
|
||||
}
|
||||
var v: f32;
|
||||
if (soft > 1e-6) {
|
||||
v = clamp((d - tol) / soft, 0.0, 1.0);
|
||||
} else if (d > tol) {
|
||||
v = 1.0;
|
||||
} else {
|
||||
v = 0.0;
|
||||
}
|
||||
if (P.u[0].z != 0u) {
|
||||
v = 1.0 - v;
|
||||
}
|
||||
let k = clamp(v, 0.0, 1.0);
|
||||
if (P.u[0].w == 2u) {
|
||||
let m = px.w * k;
|
||||
px = vec4<f32>(m, m, m, 1.0);
|
||||
} else {
|
||||
px = px * k;
|
||||
}
|
||||
}
|
||||
default: {}
|
||||
}
|
||||
textureStore(out, p, px);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Lumetri Color (color3)
|
||||
|
||||
fn fxc_tone_curve(x: f32, s: f32, m: f32, h: f32) -> f32 {
|
||||
if (s == 0.0 && m == 0.0 && h == 0.0) {
|
||||
return x;
|
||||
}
|
||||
let t = clamp(x, 0.0, 1.0);
|
||||
let d0 = (t - 0.25) / 0.25;
|
||||
let d1 = (t - 0.5) / 0.25;
|
||||
let d2 = (t - 0.75) / 0.25;
|
||||
let b0 = max(1.0 - d0 * d0, 0.0);
|
||||
let b1 = max(1.0 - d1 * d1, 0.0);
|
||||
let b2 = max(1.0 - d2 * d2, 0.0);
|
||||
return x + 0.25 * (s * (b0 * b0) + m * (b1 * b1) + h * (b2 * b2));
|
||||
}
|
||||
|
||||
fn fxc_d3(i: u32) -> vec3<f32> {
|
||||
return vec3<f32>(data[i], data[i + 1u], data[i + 2u]);
|
||||
}
|
||||
|
||||
// data: 0 temperature, 1 tint, 2 exposure gain, 3 contrast, 4 highlights, 5 shadows, 6 whites,
|
||||
// 7 blacks, 8 saturation, 9 look intensity, 10 faded film, 11 vibrance, 12 creative saturation,
|
||||
// 13 tint balance pivot, 14..17 shadow tint, 17..20 highlight tint, 20..32 curves (master, red,
|
||||
// green, blue × shadows / midtones / highlights), 32..41 wheels (shadows, midtones, highlights),
|
||||
// 41 vignette amount, 42 vignette start, 43 roundness, 44 feather, 45 cx, 46 cy, 47 lw, 48 lh,
|
||||
// 49 aspect.
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxc_lumetri(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = fxc_pixel(gid);
|
||||
if (p.x < 0) {
|
||||
return;
|
||||
}
|
||||
var px = textureLoad(src, p, 0);
|
||||
let a = px.w;
|
||||
if (a <= 0.0) {
|
||||
textureStore(out, p, px);
|
||||
return;
|
||||
}
|
||||
var c = fxc_unpremul(px);
|
||||
let temp = data[0];
|
||||
let tint = data[1];
|
||||
if (temp != 0.0 || tint != 0.0) {
|
||||
c = vec3<f32>(c.x * (1.0 + 0.25 * temp), c.y * (1.0 - 0.25 * tint), c.z * (1.0 - 0.25 * temp));
|
||||
}
|
||||
let expo = data[2];
|
||||
if (expo != 1.0) {
|
||||
c = c * expo;
|
||||
}
|
||||
let contrast = data[3];
|
||||
if (contrast != 0.0) {
|
||||
c = 0.5 + (c - 0.5) * (1.0 + contrast);
|
||||
}
|
||||
let hi = data[4];
|
||||
let sh = data[5];
|
||||
let wh = data[6];
|
||||
let bl = data[7];
|
||||
if (hi != 0.0 || sh != 0.0 || wh != 0.0 || bl != 0.0) {
|
||||
let l = max(luminance(c), 0.0);
|
||||
let lt = min(l, 1.0);
|
||||
let il = 1.0 - lt;
|
||||
let d = 0.25 * hi * fxc_smoothstep(0.5, 1.0, lt) + 0.25 * sh * (1.0 - fxc_smoothstep(0.0, 0.5, lt)) + 0.2 * wh * lt * lt + 0.2 * bl * (il * il);
|
||||
c = c + d;
|
||||
}
|
||||
let l0 = luminance(c);
|
||||
let sat = data[8];
|
||||
if (sat != 1.0) {
|
||||
c = l0 + (c - l0) * sat;
|
||||
}
|
||||
let intensity = data[9];
|
||||
let faded = data[10];
|
||||
let vib = data[11];
|
||||
let csat = data[12];
|
||||
let st = fxc_d3(14u);
|
||||
let ht = fxc_d3(17u);
|
||||
let zero = vec3<f32>(0.0);
|
||||
if (intensity != 0.0 && (faded != 0.0 || vib != 0.0 || csat != 1.0 || any(st != zero) || any(ht != zero))) {
|
||||
var d = c;
|
||||
if (faded != 0.0) {
|
||||
d = d * (1.0 - 0.25 * faded) + 0.12 * faded;
|
||||
}
|
||||
let l = luminance(d);
|
||||
if (vib != 0.0) {
|
||||
let mx = max(max(d.x, d.y), d.z);
|
||||
let mn = min(min(d.x, d.y), d.z);
|
||||
var s = 0.0;
|
||||
if (mx > 1e-6) {
|
||||
s = (mx - mn) / mx;
|
||||
}
|
||||
let k = 1.0 + vib * (1.0 - clamp(s, 0.0, 1.0));
|
||||
d = l + (d - l) * k;
|
||||
}
|
||||
if (csat != 1.0) {
|
||||
d = l + (d - l) * csat;
|
||||
}
|
||||
let pivot = data[13];
|
||||
let lt = clamp(l, 0.0, 1.0);
|
||||
let wsh = 1.0 - fxc_smoothstep(0.0, pivot, lt);
|
||||
let whi = fxc_smoothstep(pivot, 1.0, lt);
|
||||
d = d + 0.5 * (st * wsh + ht * whi);
|
||||
c = c + (d - c) * intensity;
|
||||
}
|
||||
for (var i = 0u; i < 3u; i++) {
|
||||
c[i] = fxc_tone_curve(c[i], data[20], data[21], data[22]);
|
||||
}
|
||||
for (var i = 0u; i < 3u; i++) {
|
||||
let b = 23u + i * 3u;
|
||||
c[i] = fxc_tone_curve(c[i], data[b], data[b + 1u], data[b + 2u]);
|
||||
}
|
||||
let ws = fxc_d3(32u);
|
||||
let wm = fxc_d3(35u);
|
||||
let wh3 = fxc_d3(38u);
|
||||
if (any(ws != zero) || any(wm != zero) || any(wh3 != zero)) {
|
||||
let lt = clamp(luminance(c), 0.0, 1.0);
|
||||
let ka = 1.0 - fxc_smoothstep(0.0, 0.5, lt);
|
||||
let kz = fxc_smoothstep(0.5, 1.0, lt);
|
||||
let km = 1.0 - ka - kz;
|
||||
c = c + 0.5 * (ws * ka + wm * km + wh3 * kz);
|
||||
}
|
||||
let vig = data[41];
|
||||
if (vig != 0.0) {
|
||||
let x = f32(p.x) + 0.5;
|
||||
let y = f32(p.y) + 0.5;
|
||||
let lw = data[47];
|
||||
let lh = data[48];
|
||||
var nx = (x - data[45]) / max(lw * 0.5, 1e-6);
|
||||
var ny = (y - data[46]) / max(lh * 0.5, 1e-6);
|
||||
let vround = data[43];
|
||||
let aspect = data[49];
|
||||
if (vround > 0.0) {
|
||||
nx = nx * (1.0 + (max(aspect, 1.0) - 1.0) * vround);
|
||||
ny = ny * (1.0 + (max(1.0 / aspect, 1.0) - 1.0) * vround);
|
||||
}
|
||||
let pw = 2.0 + max(-vround, 0.0) * 6.0;
|
||||
let r = powz(powz(abs(nx), pw) + powz(abs(ny), pw), 1.0 / pw) / 1.4142135;
|
||||
let start = data[42];
|
||||
let f = fxc_smoothstep(start, start + 0.05 + data[44] * (1.05 - start), r);
|
||||
if (vig > 0.0) {
|
||||
c = c + (1.0 - c) * min(vig * 0.2 * f, 1.0);
|
||||
} else {
|
||||
let k = 1.0 + vig * 0.2 * f;
|
||||
c = c * max(k, 0.0);
|
||||
}
|
||||
}
|
||||
c = max(c, zero);
|
||||
textureStore(out, p, vec4<f32>(c * a, a));
|
||||
}
|
||||
|
||||
// Straight-colour luminance plane (util::Plane::luma) in x.
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxc_luma(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = fxc_pixel(gid);
|
||||
if (p.x < 0) {
|
||||
return;
|
||||
}
|
||||
let px = textureLoad(src, p, 0);
|
||||
textureStore(out, p, vec4<f32>(luminance(fxc_unpremul(px)), 0.0, 0.0, 0.0));
|
||||
}
|
||||
|
||||
// Lumetri Sharpen: `src` + (luma − blurred luma `aux`.x) × amount. f[0].x = sharpen × 2.
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxc_sharpen(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = fxc_pixel(gid);
|
||||
if (p.x < 0) {
|
||||
return;
|
||||
}
|
||||
let px = textureLoad(src, p, 0);
|
||||
let a = px.w;
|
||||
if (a <= 0.0) {
|
||||
textureStore(out, p, px);
|
||||
return;
|
||||
}
|
||||
let c = fxc_unpremul(px);
|
||||
let d = (luminance(c) - textureLoad(aux, p, 0).x) * P.f[0].x;
|
||||
textureStore(out, p, vec4<f32>(max(c + d, vec3<f32>(0.0)) * a, a));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- planes (util)
|
||||
|
||||
// One morphology pass (util::minmax_row, edges repeated) of radius u[0].x along rows (u[0].y = 0)
|
||||
// or columns; u[1] = per-channel operation: 0 min, 1 max, 2 copy.
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxc_morph(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = fxc_pixel(gid);
|
||||
if (p.x < 0) {
|
||||
return;
|
||||
}
|
||||
let dims = out_dims();
|
||||
let r = i32(P.u[0].x);
|
||||
let vertical = P.u[0].y != 0u;
|
||||
let modes = P.u[1];
|
||||
let centre = textureLoad(src, p, 0);
|
||||
var lo = centre;
|
||||
var hi = centre;
|
||||
for (var k = -r; k <= r; k++) {
|
||||
var q = p;
|
||||
if (vertical) {
|
||||
q.y = clamp(p.y + k, 0, dims.y - 1);
|
||||
} else {
|
||||
q.x = clamp(p.x + k, 0, dims.x - 1);
|
||||
}
|
||||
let v = textureLoad(src, q, 0);
|
||||
lo = min(lo, v);
|
||||
hi = max(hi, v);
|
||||
}
|
||||
var o = centre;
|
||||
for (var i = 0; i < 4; i++) {
|
||||
if (modes[i] == 0u) {
|
||||
o[i] = lo[i];
|
||||
} else if (modes[i] == 1u) {
|
||||
o[i] = hi[i];
|
||||
}
|
||||
}
|
||||
textureStore(out, p, o);
|
||||
}
|
||||
|
||||
// `src` + (`aux` − `src`) × f[0].x (Plane::zip_map lerp).
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxc_lerp(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = fxc_pixel(gid);
|
||||
if (p.x < 0) {
|
||||
return;
|
||||
}
|
||||
let x = textureLoad(src, p, 0);
|
||||
let y = textureLoad(aux, p, 0);
|
||||
textureStore(out, p, x + (y - x) * P.f[0].x);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Key Light (keylight.rs)
|
||||
|
||||
fn fxc_neutralise(c: vec3<f32>, bias: vec3<f32>) -> vec3<f32> {
|
||||
let l = max(luminance(bias), 1e-4);
|
||||
return vec3<f32>(c.x * l / max(bias.x, 1e-4), c.y * l / max(bias.y, 1e-4), c.z * l / max(bias.z, 1e-4));
|
||||
}
|
||||
|
||||
fn fxc_screen_diff(c: vec3<f32>, ix: vec3<u32>, bal: f32) -> f32 {
|
||||
return c[ix.x] - (bal * c[ix.y] + (1.0 - bal) * c[ix.z]);
|
||||
}
|
||||
|
||||
fn fxc_correct(c0: vec3<f32>, sat: f32, contrast: f32, bright: f32) -> vec3<f32> {
|
||||
let l = luminance(c0);
|
||||
let c = l + (c0 - l) * sat;
|
||||
return ((c - 0.5) * contrast + 0.5) * bright;
|
||||
}
|
||||
|
||||
// Raw screen matte of `src` (the key source): out = (clipped, clipped, raw, 0).
|
||||
// u[0].xyz = primary, other channels; f[0] = (alpha bias, screen difference);
|
||||
// f[1] = (gain, balance, clip black, clip white).
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxc_kl_raw(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = fxc_pixel(gid);
|
||||
if (p.x < 0) {
|
||||
return;
|
||||
}
|
||||
let c = fxc_unpremul(textureLoad(src, p, 0));
|
||||
let d = fxc_screen_diff(fxc_neutralise(c, P.f[0].xyz), P.u[0].xyz, P.f[1].y);
|
||||
let raw = clamp(1.0 - P.f[1].x * d / P.f[0].w, 0.0, 1.0);
|
||||
let cb = P.f[1].z;
|
||||
let m = clamp((raw - cb) / max(P.f[1].w - cb, 1e-4), 0.0, 1.0);
|
||||
textureStore(out, p, vec4<f32>(m, m, raw, 0.0));
|
||||
}
|
||||
|
||||
// Clip Rollback: `src` = (shrunk, grown) clipped matte, `aux` = raw planes (clipped, _, raw).
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxc_kl_rollback(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = fxc_pixel(gid);
|
||||
if (p.x < 0) {
|
||||
return;
|
||||
}
|
||||
let sg = textureLoad(src, p, 0);
|
||||
let r = textureLoad(aux, p, 0);
|
||||
var m = r.x;
|
||||
if (sg.y > sg.x + 1e-4) {
|
||||
m = min(max(m, r.z), sg.y);
|
||||
}
|
||||
textureStore(out, p, vec4<f32>(m, m, r.z, 0.0));
|
||||
}
|
||||
|
||||
// Planes for the final pass: (screen matte from `src`.x, raw from `aux`.z, edge 0, 0).
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxc_kl_pack(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = fxc_pixel(gid);
|
||||
if (p.x < 0) {
|
||||
return;
|
||||
}
|
||||
textureStore(out, p, vec4<f32>(textureLoad(src, p, 0).x, textureLoad(aux, p, 0).z, 0.0, 0.0));
|
||||
}
|
||||
|
||||
// Combined matte from the screen matte and the source alpha (no inside / outside masks).
|
||||
fn fxc_kl_combined(sm: f32, src_a: f32, mode: u32) -> f32 {
|
||||
var a = sm;
|
||||
let sa = clamp(src_a, 0.0, 1.0);
|
||||
var ins = 0.0;
|
||||
if (mode == 1u) {
|
||||
ins = max(ins, sa);
|
||||
}
|
||||
a = max(a, ins);
|
||||
if (mode == 2u) {
|
||||
a = a * sa;
|
||||
}
|
||||
return clamp(a, 0.0, 1.0);
|
||||
}
|
||||
|
||||
// Edge band of the combined matte: `src` = source, `aux` = planes; u[0].x = source alpha mode.
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxc_kl_band(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = fxc_pixel(gid);
|
||||
if (p.x < 0) {
|
||||
return;
|
||||
}
|
||||
let a = fxc_kl_combined(textureLoad(aux, p, 0).x, textureLoad(src, p, 0).w, P.u[0].x);
|
||||
textureStore(out, p, vec4<f32>(select(0.0, 1.0, a > 1e-3 && a < 0.999), 0.0, 0.0, 0.0));
|
||||
}
|
||||
|
||||
// Planes `src` with the edge weight from the processed band `aux`.x; f[0].x = hardness.
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxc_kl_edge(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = fxc_pixel(gid);
|
||||
if (p.x < 0) {
|
||||
return;
|
||||
}
|
||||
let pl = textureLoad(src, p, 0);
|
||||
let e = min(textureLoad(aux, p, 0).x * (1.0 + P.f[0].x * 4.0), 1.0);
|
||||
textureStore(out, p, vec4<f32>(pl.x, pl.y, e, 0.0));
|
||||
}
|
||||
|
||||
fn fxc_kl_replace(fg: vec3<f32>, c: vec3<f32>, method: u32, rc: vec3<f32>, amt: f32) -> vec3<f32> {
|
||||
if (amt <= 0.0) {
|
||||
return fg;
|
||||
}
|
||||
switch method {
|
||||
case 1u: { return fg + (c - fg) * amt; }
|
||||
case 2u: { return rc; }
|
||||
case 3u: {
|
||||
let l = luminance(c) / max(luminance(rc), 1e-4);
|
||||
return fg + (rc * l - fg) * amt;
|
||||
}
|
||||
default: { return fg; }
|
||||
}
|
||||
}
|
||||
|
||||
// Final pass: `src` = source, `aux` = planes (screen matte, raw, edge).
|
||||
// u[0] = (primary, others, view); u[1] = (replace method, foreground cc, unpremultiply,
|
||||
// source alpha mode); f[0] = (screen, balance); f[1] = (despill bias, l0); f[2] = replace colour;
|
||||
// f[3] = fg cc (sat, contrast, bright); f[4] = edge cc; f[5] = crops (l, r, t, b);
|
||||
// f[6] = (offset, scale, _); f[7] = (layer w, layer h).
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxc_kl_final(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = fxc_pixel(gid);
|
||||
if (p.x < 0) {
|
||||
return;
|
||||
}
|
||||
let s = textureLoad(src, p, 0);
|
||||
let pl = textureLoad(aux, p, 0);
|
||||
let ix = P.u[0].xyz;
|
||||
let view = P.u[0].w;
|
||||
let c = fxc_unpremul(s);
|
||||
let sm = pl.x;
|
||||
let raw = pl.y;
|
||||
let edge = pl.z;
|
||||
let a = fxc_kl_combined(sm, s.w, P.u[1].w);
|
||||
let screen = P.f[0].xyz;
|
||||
let bal = P.f[0].w;
|
||||
let dbias = P.f[1].xyz;
|
||||
let fg_cc = P.u[1].y != 0u;
|
||||
let ar = max(raw, 1e-4);
|
||||
var fg = max((c - (1.0 - ar) * screen) / ar, vec3<f32>(0.0));
|
||||
let spill = max(fxc_screen_diff(fxc_neutralise(fg, dbias), ix, bal), 0.0);
|
||||
fg[ix.x] -= spill / P.f[1].w;
|
||||
let intermediate = fg;
|
||||
fg = fxc_kl_replace(fg, c, P.u[1].x, P.f[2].xyz, max(sm - raw, 0.0));
|
||||
if (fg_cc) {
|
||||
fg = fxc_correct(fg, P.f[3].x, P.f[3].y, P.f[3].z);
|
||||
}
|
||||
if (edge > 0.0) {
|
||||
let ce = fxc_correct(fg, P.f[4].x, P.f[4].y, P.f[4].z);
|
||||
fg = fg + (ce - fg) * edge;
|
||||
}
|
||||
var o: vec4<f32>;
|
||||
switch view {
|
||||
case 1u: { o = vec4<f32>(s.w, s.w, s.w, 1.0); }
|
||||
case 2u: {
|
||||
var cs = fxc_neutralise(c, dbias);
|
||||
let s2 = max(fxc_screen_diff(cs, ix, bal), 0.0);
|
||||
cs[ix.x] -= s2;
|
||||
if (fg_cc) {
|
||||
cs = fxc_correct(cs, P.f[3].x, P.f[3].y, P.f[3].z);
|
||||
}
|
||||
o = vec4<f32>(cs, 1.0);
|
||||
}
|
||||
case 3u: { o = vec4<f32>(edge, edge, edge, 1.0); }
|
||||
case 4u: { o = vec4<f32>(sm, sm, sm, 1.0); }
|
||||
case 5u, 6u: { o = vec4<f32>(0.0, 0.0, 0.0, 1.0); }
|
||||
case 7u: { o = vec4<f32>(a, a, a, 1.0); }
|
||||
case 8u: {
|
||||
var v = 0.5;
|
||||
if (a <= 1e-3) {
|
||||
v = 0.0;
|
||||
} else if (a >= 0.999) {
|
||||
v = 1.0;
|
||||
}
|
||||
o = vec4<f32>(v, v, v, 1.0);
|
||||
}
|
||||
case 9u: { o = vec4<f32>(intermediate * sm, sm); }
|
||||
default: {
|
||||
let x = f32(p.x) + 0.5;
|
||||
let y = f32(p.y) + 0.5;
|
||||
let lx = (x - P.f[6].x) / P.f[6].z / P.f[7].x;
|
||||
let ly = (y - P.f[6].y) / P.f[6].z / P.f[7].y;
|
||||
let cr = P.f[5];
|
||||
let cropped = lx < cr.x || lx > 1.0 - cr.y || ly < cr.z || ly > 1.0 - cr.w;
|
||||
if (cropped) {
|
||||
o = vec4<f32>(0.0);
|
||||
} else if (P.u[1].z != 0u) {
|
||||
o = vec4<f32>(fg * a, a);
|
||||
} else {
|
||||
o = vec4<f32>(fg * a * a, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
textureStore(out, p, o);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,163 @@
|
|||
//! GPU colour-family effects vs the CPU effects (the oracle): direct on a buffer at full and
|
||||
//! half resolution, as adjustment, and composited at 8 and 32 bpc.
|
||||
|
||||
use effectcraft_keyframe::Value;
|
||||
|
||||
use crate::tests::{c, effect_case, n};
|
||||
|
||||
fn e(v: u32) -> Value {
|
||||
Value::Enum(v)
|
||||
}
|
||||
|
||||
fn on() -> Value {
|
||||
Value::Bool(true)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn color_balance_vibrance_tritone_mixer() {
|
||||
effect_case("ec.color.colorbalance", &[("shadowRed", n(40.0)), ("midGreen", n(-30.0)), ("hiBlue", n(60.0))]);
|
||||
effect_case("ec.color.colorbalance", &[("shadowBlue", n(-50.0)), ("midRed", n(70.0)), ("hiGreen", n(25.0)), ("preserveLuminosity", on())]);
|
||||
effect_case("ec.color.vibrance", &[("vibrance", n(60.0)), ("saturation", n(-20.0))]);
|
||||
effect_case("ec.color.vibrance", &[("vibrance", n(-40.0)), ("saturation", n(35.0))]);
|
||||
effect_case("ec.color.tritone", &[("midtones", c(0.6, 0.3, 0.5)), ("shadows", c(0.0, 0.1, 0.3)), ("blend", n(30.0))]);
|
||||
effect_case("ec.color.tritone", &[("highlights", c(1.0, 0.9, 0.6))]);
|
||||
effect_case("ec.color.channelmixer", &[("rr", n(80.0)), ("rg", n(30.0)), ("gb", n(-40.0)), ("bc", n(10.0)), ("br", n(50.0))]);
|
||||
effect_case("ec.color.channelmixer", &[("rr", n(40.0)), ("rg", n(40.0)), ("rb", n(20.0)), ("rc", n(-5.0)), ("monochrome", on())]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn black_white_colorama_selective_color() {
|
||||
effect_case("ec.color.blackwhite", &[]);
|
||||
effect_case("ec.color.blackwhite", &[("reds", n(120.0)), ("blues", n(-50.0)), ("cyans", n(200.0)), ("tint", on())]);
|
||||
effect_case("ec.color.colorama", &[]);
|
||||
effect_case("ec.color.colorama", &[("inputPhase/mode", e(4)), ("inputPhase/phaseShift", n(75.0)), ("outputCycle/cycles", n(2.5)), ("blend", n(40.0))]);
|
||||
effect_case("ec.color.colorama", &[("inputPhase/mode", e(8)), ("outputCycle/cycles", n(1.3))]);
|
||||
effect_case("ec.color.selectivecolor", &[("redsCyan", n(60.0)), ("bluesYellow", n(-40.0)), ("neutralsBlack", n(20.0)), ("whitesMagenta", n(30.0))]);
|
||||
effect_case(
|
||||
"ec.color.selectivecolor",
|
||||
&[("method", e(1)), ("greensMagenta", n(50.0)), ("yellowsCyan", n(-30.0)), ("blacksYellow", n(40.0)), ("magentasBlack", n(25.0))],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lumetri() {
|
||||
effect_case(
|
||||
"ec.color.lumetri",
|
||||
&[
|
||||
("basicCorrection/whiteBalance/temperature", n(30.0)),
|
||||
("basicCorrection/whiteBalance/tint", n(-20.0)),
|
||||
("basicCorrection/tone/exposure", n(0.5)),
|
||||
("basicCorrection/tone/contrast", n(25.0)),
|
||||
("basicCorrection/tone/highlights", n(-40.0)),
|
||||
("basicCorrection/tone/shadows", n(30.0)),
|
||||
("basicCorrection/tone/whites", n(10.0)),
|
||||
("basicCorrection/tone/blacks", n(-15.0)),
|
||||
("basicCorrection/saturation", n(130.0)),
|
||||
],
|
||||
);
|
||||
effect_case(
|
||||
"ec.color.lumetri",
|
||||
&[
|
||||
("creative/lookIntensity", n(80.0)),
|
||||
("creative/adjustments/fadedFilm", n(30.0)),
|
||||
("creative/adjustments/vibrance", n(40.0)),
|
||||
("creative/adjustments/creativeSaturation", n(70.0)),
|
||||
("creative/adjustments/shadowTint", c(0.3, 0.5, 0.7)),
|
||||
("creative/adjustments/highlightTint", c(0.7, 0.55, 0.4)),
|
||||
("creative/adjustments/tintBalance", n(-30.0)),
|
||||
("curves/curveMasterMidtones", n(30.0)),
|
||||
("curves/curveRedShadows", n(-20.0)),
|
||||
("curves/curveBlueHighlights", n(40.0)),
|
||||
],
|
||||
);
|
||||
effect_case(
|
||||
"ec.color.lumetri",
|
||||
&[
|
||||
("colorWheels/shadowsWheel", c(0.4, 0.5, 0.6)),
|
||||
("colorWheels/highlightsWheel", c(0.6, 0.5, 0.45)),
|
||||
("vignette/vignetteAmount", n(-2.0)),
|
||||
("vignette/vignetteMidpoint", n(30.0)),
|
||||
("vignette/vignetteRoundness", n(40.0)),
|
||||
("creative/adjustments/sharpen", n(50.0)),
|
||||
],
|
||||
);
|
||||
effect_case(
|
||||
"ec.color.lumetri",
|
||||
&[
|
||||
("vignette/vignetteAmount", n(1.5)),
|
||||
("vignette/vignetteRoundness", n(-60.0)),
|
||||
("vignette/vignetteFeather", n(20.0)),
|
||||
("creative/adjustments/sharpen", n(-40.0)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn linear_color_key() {
|
||||
effect_case("ec.key.linearcolor", &[("keyColor", c(0.6, 0.5, 0.4)), ("tolerance", n(15.0)), ("softness", n(20.0))]);
|
||||
effect_case("ec.key.linearcolor", &[("keyColor", c(0.2, 0.8, 0.3)), ("matchColors", e(1)), ("keyOperation", e(1)), ("view", e(2))]);
|
||||
effect_case("ec.key.linearcolor", &[("keyColor", c(0.7, 0.3, 0.6)), ("matchColors", e(2)), ("tolerance", n(5.0)), ("softness", n(30.0))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_light() {
|
||||
let screen = ("screenColour", c(0.3, 0.75, 0.35));
|
||||
effect_case("ec.keying.keylight", std::slice::from_ref(&screen));
|
||||
effect_case(
|
||||
"ec.keying.keylight",
|
||||
&[
|
||||
// A low gain against a pure screen keeps the raw matte away from 0 (the screen
|
||||
// removal divides by it; near 0 it amplifies the blurs' float rounding).
|
||||
("screenColour", c(0.0, 1.0, 0.0)),
|
||||
("screenGain", n(60.0)),
|
||||
("screenBalance", n(30.0)),
|
||||
("screenPreblur", n(3.0)),
|
||||
("screenMatte/clipBlack", n(10.0)),
|
||||
("screenMatte/clipWhite", n(80.0)),
|
||||
("screenMatte/clipRollback", n(2.5)),
|
||||
("screenMatte/screenSoftness", n(3.0)),
|
||||
("screenMatte/replaceMethod", e(3)),
|
||||
("screenMatte/replaceColour", c(0.6, 0.4, 0.3)),
|
||||
("unpremultiplyResult", Value::Bool(false)),
|
||||
],
|
||||
);
|
||||
effect_case(
|
||||
"ec.keying.keylight",
|
||||
&[
|
||||
("screenColour", c(0.2, 0.4, 0.8)),
|
||||
("lockBiasesTogether", Value::Bool(false)),
|
||||
("alphaBias", c(0.5, 0.45, 0.6)),
|
||||
("despillBias", c(0.55, 0.5, 0.45)),
|
||||
("screenMatte/clipWhite", n(70.0)),
|
||||
("screenMatte/screenDespotBlack", n(2.0)),
|
||||
("screenMatte/screenDespotWhite", n(1.5)),
|
||||
("screenMatte/screenShrinkGrow", n(-1.5)),
|
||||
("screenMatte/replaceMethod", e(2)),
|
||||
("foregroundColourCorrection/enableColourCorrection", on()),
|
||||
("foregroundColourCorrection/saturation", n(120.0)),
|
||||
("foregroundColourCorrection/contrast", n(15.0)),
|
||||
("edgeColourCorrection/enableEdgeColourCorrection", on()),
|
||||
("edgeColourCorrection/edgeSoftness", n(2.0)),
|
||||
("edgeColourCorrection/edgeHardness", n(30.0)),
|
||||
("edgeColourCorrection/edgeBrightness", n(-20.0)),
|
||||
("sourceCrops/cropLeft", n(13.0)),
|
||||
("sourceCrops/cropBottom", n(7.0)),
|
||||
("insideMask/sourceAlpha", e(1)),
|
||||
],
|
||||
);
|
||||
// Soft Colour without Screen Softness (after the blur, the screen-removed foreground of
|
||||
// nearly transparent pixels, divided by a raw matte near 1e-4, amplifies float rounding).
|
||||
effect_case(
|
||||
"ec.keying.keylight",
|
||||
&[
|
||||
screen.clone(),
|
||||
("screenMatte/clipBlack", n(15.0)),
|
||||
("screenMatte/clipRollback", n(1.5)),
|
||||
("screenMatte/replaceColour", c(0.6, 0.4, 0.3)),
|
||||
("insideMask/sourceAlpha", e(0)),
|
||||
],
|
||||
);
|
||||
for view in [1, 2, 4, 7, 9] {
|
||||
effect_case("ec.keying.keylight", &[screen.clone(), ("view", e(view)), ("screenMatte/screenShrinkGrow", n(1.0)), ("screenMatte/replaceMethod", e(1))]);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
EffectCraft ships 303 effects with 2434 parameters in total, grouped into the same categories as the Effects & Presets panel. Every effect is our own implementation, written from public behaviour descriptions and standard image-processing literature. Parameter names, order, popup options, units, defaults and ranges follow the reference application so that projects, expressions and muscle memory carry over.
|
||||
|
||||
- **GPU**: 24 effects also run on the GPU compositor with identical results.
|
||||
- **GPU**: 34 effects also run on the GPU compositor with identical results.
|
||||
- **32**: 303 effects process 32-bit float (HDR, overbright) pixels without clamping.
|
||||
- **Status**: 261 implemented in full, 42 partial (what is missing is listed).
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
|||
| Auto Color | `ec.color.autocolor` | 6 | | 32 | Implemented |
|
||||
| Auto Contrast | `ec.color.autocontrast` | 5 | | 32 | Implemented |
|
||||
| Auto Levels | `ec.color.autolevels` | 5 | | 32 | Implemented |
|
||||
| Black & White | `ec.color.blackwhite` | 8 | | 32 | Implemented |
|
||||
| Black & White | `ec.color.blackwhite` | 8 | GPU | 32 | Implemented |
|
||||
| Brightness & Contrast | `ec.color.brightnesscontrast` | 3 | GPU | 32 | Implemented |
|
||||
| Broadcast Colors | `ec.color.broadcast` | 3 | | 32 | Implemented |
|
||||
| CC Color Neutralizer | `ec.color.cccolorneutralizer` | 11 | | 32 | Implemented |
|
||||
|
|
@ -97,12 +97,12 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
|||
| CC Toner | `ec.color.cctoner` | 7 | | 32 | Implemented |
|
||||
| Change Color | `ec.color.changecolor` | 9 | | 32 | Implemented |
|
||||
| Change to Color | `ec.color.changetocolor` | 9 | | 32 | Implemented |
|
||||
| Channel Mixer | `ec.color.channelmixer` | 13 | | 32 | Implemented |
|
||||
| Color Balance | `ec.color.colorbalance` | 10 | | 32 | Implemented |
|
||||
| Channel Mixer | `ec.color.channelmixer` | 13 | GPU | 32 | Implemented |
|
||||
| Color Balance | `ec.color.colorbalance` | 10 | GPU | 32 | Implemented |
|
||||
| Color Balance (HLS) | `ec.color.colorbalancehls` | 3 | | 32 | Implemented |
|
||||
| Color Link | `ec.color.colorlink` | 6 | | 32 | Implemented |
|
||||
| Color Stabilizer | `ec.color.colorstabilizer` | 6 | | 32 | Implemented |
|
||||
| Colorama | `ec.color.colorama` | 5 | | 32 | Partial: no Add Phase, editable Output Cycle, Modify, Pixel Selection or Masking |
|
||||
| Colorama | `ec.color.colorama` | 5 | GPU | 32 | Partial: no Add Phase, editable Output Cycle, Modify, Pixel Selection or Masking |
|
||||
| Curves | `ec.color.curves` | 0 | GPU | 32 | Partial: no Channel popup |
|
||||
| Equalize | `ec.color.equalize` | 2 | | 32 | Implemented |
|
||||
| Exposure | `ec.color.exposure` | 14 | GPU | 32 | Implemented |
|
||||
|
|
@ -111,7 +111,7 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
|||
| Leave Color | `ec.color.leavecolor` | 5 | | 32 | Implemented |
|
||||
| Levels | `ec.color.levels` | 7 | GPU | 32 | Partial: master channel only (no Channel popup) |
|
||||
| Levels (Individual Controls) | `ec.color.levelsic` | 27 | | 32 | Implemented |
|
||||
| Lumetri Color | `ec.color.lumetri` | 38 | | 32 | Partial: no HSL Secondary, hue/saturation curves, HDR mode or look files |
|
||||
| Lumetri Color | `ec.color.lumetri` | 38 | GPU | 32 | Partial: no HSL Secondary, hue/saturation curves, HDR mode or look files |
|
||||
| OCIO CDL Transform | `ec.color.ociocdl` | 12 | | 32 | Implemented |
|
||||
| OCIO Color Space Transform | `ec.color.ociocolorspace` | 4 | | 32 | Implemented |
|
||||
| OCIO Display Transform | `ec.color.ociodisplay` | 5 | | 32 | Implemented |
|
||||
|
|
@ -119,11 +119,11 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
|||
| OCIO Look Transform | `ec.color.ociolook` | 6 | | 32 | Implemented |
|
||||
| PS Arbitrary Map | `ec.color.psarbitrarymap` | 2 | | 32 | Implemented |
|
||||
| Photo Filter | `ec.color.photofilter` | 4 | | 32 | Implemented |
|
||||
| Selective Color | `ec.color.selectivecolor` | 37 | | 32 | Partial: simplified Colors / Details layout |
|
||||
| Selective Color | `ec.color.selectivecolor` | 37 | GPU | 32 | Partial: simplified Colors / Details layout |
|
||||
| Shadow/Highlight | `ec.color.shadowhighlight` | 14 | | 32 | Implemented |
|
||||
| Tint | `ec.color.tint` | 3 | GPU | 32 | Implemented |
|
||||
| Tritone | `ec.color.tritone` | 4 | | 32 | Implemented |
|
||||
| Vibrance | `ec.color.vibrance` | 2 | | 32 | Implemented |
|
||||
| Tritone | `ec.color.tritone` | 4 | GPU | 32 | Implemented |
|
||||
| Vibrance | `ec.color.vibrance` | 2 | GPU | 32 | Implemented |
|
||||
| Video Limiter | `ec.color.videolimiter` | 5 | | 32 | Implemented |
|
||||
|
||||
## Distort
|
||||
|
|
@ -243,8 +243,8 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
|||
| Extract | `ec.key.extract` | 6 | | 32 | Implemented |
|
||||
| Inner/Outer Key | `ec.key.innerouter` | 10 | | 32 | Partial: one additional mask per side; no cleanup strokes |
|
||||
| Key Cleaner | `ec.key.keycleaner` | 4 | | 32 | Implemented |
|
||||
| Key Light | `ec.keying.keylight` | 42 | | 32 | Partial: no Source Crops X/Y Method or Edge Colour; no colour suppression / balancing |
|
||||
| Linear Color Key | `ec.key.linearcolor` | 6 | | 32 | Implemented |
|
||||
| Key Light | `ec.keying.keylight` | 42 | GPU | 32 | Partial: no Source Crops X/Y Method or Edge Colour; no colour suppression / balancing |
|
||||
| Linear Color Key | `ec.key.linearcolor` | 6 | GPU | 32 | Implemented |
|
||||
| Screen Key | `ec.key.screen` | 11 | | 32 | Implemented |
|
||||
| Unmult | `ec.key.unmult` | 6 | | 32 | Implemented |
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue