Merge branch 'main' into worktree-agent-a15cecfec57d5290d

# Conflicts:
#	crates/effects/src/ocio.rs
#	crates/engine/src/commands/stubs.rs
#	docs/parity.md
This commit is contained in:
Brandon Thomas
2026-10-03 02:26:19 -04:00
151 changed files with 14085 additions and 2190 deletions
+35
View File
@@ -484,6 +484,41 @@ impl<'g> Enc<'g> {
Some(out)
}
/// [`Enc::read_texture`] without waiting for the GPU (the browser's main thread can't): `done`
/// gets the bytes once the copy is mapped (from the browser's event loop on the web; from
/// a later device poll natively).
pub fn read_texture_async(&mut self, texture: &wgpu::Texture, w: u32, h: u32, bpp: u32, done: impl FnOnce(Option<Vec<u8>>) + wgpu::WasmNotSend + 'static) {
let row = w * bpp;
let padded = row.div_ceil(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT) * wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
let buf = self.g.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("readback-async"),
size: padded as u64 * h as u64,
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.encoder().copy_texture_to_buffer(
wgpu::TexelCopyTextureInfo { texture, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All },
wgpu::TexelCopyBufferInfo { buffer: &buf, layout: wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(padded), rows_per_image: Some(h) } },
wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
);
self.submit();
let b = buf.clone();
buf.map_async(wgpu::MapMode::Read, .., move |r| {
let out = r.ok().and_then(|_| {
let view = b.get_mapped_range(..).ok()?;
let mut out = Vec::with_capacity((row * h) as usize);
for y in 0..h as usize {
out.extend_from_slice(&view[y * padded as usize..y * padded as usize + row as usize]);
}
Some(out)
});
b.unmap();
done(out);
});
#[cfg(not(target_arch = "wasm32"))]
let _ = self.g.device.poll(wgpu::PollType::Poll);
}
/// A data buffer for kernels that read tables (curves).
pub fn data(&self, v: &[f32]) -> wgpu::Buffer {
let mut bytes = Vec::with_capacity(v.len().max(4) * 4);
+61 -17
View File
@@ -179,20 +179,21 @@ fn glow(e: &mut Enc, ctx: &EffectCtx, mut b: GBuf) -> Option<GBuf> {
let use_colors = ctx.params.e("colors") == 1;
let ca = ctx.params.color("colorA");
let cb = ctx.params.color("colorB");
let (kx, ky) = dims_xy(ctx.params.e("glowDimensions"));
if !ctx.adjustment {
b.pad(e, (radius * 1.5).ceil() as u32 + 2)?;
}
let mut p = Params::default();
p.u[0][0] = use_colors as u32;
p.f[0][0] = thr;
p.u[0] = [use_colors as u32, (ctx.params.e("based") == 0) as u32, ctx.params.e("colorLooping"), 0];
p.f[0] = [thr, ctx.params.f("colorLoops") as f32, (ctx.params.f("colorPhase") / 360.0) as f32, ctx.params.f("abMidpoint") as f32 / 100.0];
p.f[1] = ca;
p.f[2] = cb;
let bright = e.image(b.img.width, b.img.height);
e.pixels("glow_bright", &p, &b.img, None, &bright, None);
let s = (radius / 2.0).max(0.5);
let blurred = gaussian_blur(e, &bright, s, s, false);
let blurred = gaussian_blur(e, &bright, s * kx, s * ky, false);
let mut p = Params::default();
p.u[0][0] = (ctx.params.e("operation") == 1) as u32;
p.u[0][0] = ctx.params.e("operation").min(2);
p.f[0][0] = intensity;
let out = e.image(b.img.width, b.img.height);
e.pixels("glow_combine", &p, &b.img, Some(&blurred), &out, None);
@@ -240,8 +241,9 @@ fn transform(e: &mut Enc, ctx: &EffectCtx, mut b: GBuf) -> Option<GBuf> {
* Mat3::skew_deg(ctx.params.f("skew"), ctx.params.f("skewAxis"))
* Mat3::scale(vec2(sw / 100.0, sh / 100.0))
* Mat3::translate(vec2(-anchor.0, -anchor.1));
let sampling = if ctx.params.e("sampling") == 1 { Sampling::Bicubic } else { Sampling::Bilinear };
let empty = e.image(b.img.width, b.img.height);
b.img = ops::warp(e, &empty, &b.img, &m, Sampling::Bilinear, effectcraft_color::BlendMode::Normal, opacity, 0, None);
b.img = ops::warp(e, &empty, &b.img, &m, sampling, effectcraft_color::BlendMode::Normal, opacity, 0, None);
Some(b)
}
@@ -292,8 +294,17 @@ fn pointwise(e: &mut Enc, id: &str, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
let br = f("brightness") as f32 / 100.0;
let ct = f("contrast") as f32 / 100.0;
let k = if ct >= 0.0 { 1.0 / (1.0 - ct * 0.99) } else { 1.0 + ct };
p.u[0][0] = 2;
p.f[0] = [br, k, 0.0, 0.0];
let legacy = ctx.params.b("useLegacy");
// Mode: 0 = identity, 1 = legacy linear, 2 = tone curves (see `bc_modern`).
let mode = if br == 0.0 && ct == 0.0 {
0
} else if legacy {
1
} else {
2
};
p.u[0] = [2, mode, 0, 0];
p.f[0] = [br, k, ct, 0.0];
}
"ec.color.huesaturation" => {
p.u[0] = [3, ctx.params.b("colorize") as u32, 0, 0];
@@ -301,16 +312,28 @@ fn pointwise(e: &mut Enc, id: &str, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
p.f[1] = [f("colorizeHue") as f32 / 360.0, f("colorizeSaturation") as f32 / 100.0, f("colorizeLightness") as f32 / 100.0, 0.0];
}
"ec.color.levels" => {
p.u[0] = [4, !ctx.params.b("noClip") as u32, 0, 0];
let (clip_b, clip_w) = effectcraft_effects::levels_clip(ctx);
p.u[0] = [4, clip_b as u32, clip_w as u32, 0];
p.f[0] = [f("inBlack") as f32, f("inWhite") as f32, f("gamma").max(0.01) as f32, f("outBlack") as f32];
p.f[1][0] = f("outWhite") as f32;
}
"ec.color.exposure" => {
p.u[0][0] = 5;
p.f[0] = [2f32.powf(f("exposure") as f32), f("offset") as f32, f("gamma").max(0.01) as f32, 0.0];
let s = effectcraft_effects::exposure_settings(ctx);
p.u[0] = [5, ctx.params.b("bypassLinearLight") as u32, 0, 0];
for i in 0..3 {
p.f[0][i] = s[i].0;
p.f[1][i] = s[i].1;
p.f[2][i] = s[i].2;
}
}
"ec.channel.invert" => {
p.u[0] = [6, ctx.params.e("channel"), 0, 0];
// The shader does the RGB channels and Alpha; HLS / YIQ inversions run on the CPU.
let ch = match ctx.params.e("channel") {
c @ 0..=3 => c,
effectcraft_effects::INVERT_ALPHA => 4,
_ => return None,
};
p.u[0] = [6, ch, 0, 0];
p.f[0][0] = 1.0 - f("blend") as f32 / 100.0;
}
"ec.generate.fill" => {
@@ -330,14 +353,35 @@ fn pointwise(e: &mut Enc, id: &str, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
p.f[3] = [len2 as f32, f("scatter") as f32 / 512.0, f("blend") as f32 / 100.0, 0.0];
}
"ec.noise.fractal" => {
// The shader covers Soft Linear noise, the Basic / Turbulent Smooth / Turbulent Basic
// types, all Overflow modes, independent width / height scaling, Sub Influence and Sub
// Scaling, and the None / Normal blending modes; anything else renders on the CPU.
let pr = ctx.params;
let kind = pr.e("fractalType");
let mode = pr.e("blendingMode");
let sub_offset = pr.v2("subSettings/subOffset");
if pr.e("noiseType") != 2
|| kind > 2
|| mode > 1
|| pr.b("evolutionOptions/cycleEvolution")
|| pr.b("transform/perspectiveOffset")
|| f("subSettings/subRotation") != 0.0
|| (!pr.b("subSettings/centerSubscale") && sub_offset != [0.0, 0.0])
{
return None;
}
let octaves = f("complexity").clamp(1.0, 20.0);
let (sr, cr) = (f("rotation") as f32).to_radians().sin_cos();
let (ox, oy) = b.to_px(ctx.params.v2("offset"));
p.u[0] = [10, ctx.params.e("fractalType"), octaves.ceil() as u32, f("seed") as u32 ^ 0x51ed];
p.u[1][0] = ctx.params.b("invert") as u32;
p.f[0] = [f("contrast") as f32 / 100.0, f("brightness") as f32 / 100.0, (f("scale") * b.scale).max(1.0) as f32, (octaves - octaves.floor()) as f32];
let (sr, cr) = (f("transform/rotation") as f32).to_radians().sin_cos();
let (ox, oy) = b.to_px(pr.v2("transform/offset"));
let s = f("transform/scale");
let (sw, sh) = if pr.b("transform/uniformScaling") { (s, s) } else { (f("transform/scaleWidth"), f("transform/scaleHeight")) };
let sub = (f("subSettings/subScaling") / 100.0).clamp(0.1, 1.0);
p.u[0] = [10, kind, octaves.ceil() as u32, f("evolutionOptions/seed") as u32 ^ 0x51ed];
p.u[1] = [pr.b("invert") as u32, pr.e("overflow"), mode, 0];
p.f[0] = [f("contrast") as f32 / 100.0, f("brightness") as f32 / 100.0, (sw * b.scale).max(1.0) as f32, (octaves - octaves.floor()) as f32];
p.f[1] = [ox as f32, oy as f32, sr, cr];
p.f[2] = [f("evolution") as f32 / 360.0, f("blend") as f32 / 100.0, f("opacity") as f32 / 100.0, 0.0];
p.f[2] = [f("evolution") as f32 / 360.0, f("blend") as f32 / 100.0, (f("opacity") as f32 / 100.0).clamp(0.0, 1.0), (sh * b.scale).max(1.0) as f32];
p.f[3] = [(f("subSettings/subInfluence") as f32 / 100.0).clamp(0.0, 1.0), 1.0 / sub as f32, 0.0, 0.0];
}
_ => return None,
}
+6
View File
@@ -93,6 +93,12 @@ impl Gpu {
Enc::new(&self.ctx).read_texture(&f.texture, f.width, f.height, 4)
}
/// [`Gpu::read_display`] without blocking (the browser's main thread): `done` gets the bytes
/// once the GPU has them.
pub fn read_display_async(&self, f: &DisplayFrame, done: impl FnOnce(Option<Vec<u8>>) + wgpu::WasmNotSend + 'static) {
Enc::new(&self.ctx).read_texture_async(&f.texture, f.width, f.height, 4, done);
}
/// Render a frame on the GPU and read it back (`None` = not handled).
pub fn render(&self, r: &Renderer, comp: ItemId, t: Tick) -> Option<Image> {
if !self.ctx.can_readback() {
+98 -18
View File
@@ -326,7 +326,29 @@ fn directional(@builtin(global_invocation_id) gid: vec3<u32>) {
// ---------------------------------------------------------------- Glow / Drop Shadow
// Glow bright pass. u[0].x = A & B colours; f[0].x = threshold; f[1] = colour A; f[2] = colour B.
// Glow's A & B gradient position (effects::misc::glow_ab_t). f[0].yzw = loops, phase, midpoint;
// u[0].z = colour looping.
fn glow_ab_t(l: f32) -> f32 {
let u = clamp(l, 0.0, 1.0) * max(P.f[0].y, 0.0) + P.f[0].z;
var t = u - floor(u);
if (u > 0.0 && t <= 0.0) {
t = 1.0;
}
let tri = 1.0 - abs(2.0 * t - 1.0);
let lp = P.u[0].z;
if (lp == 1u) {
t = 1.0 - t;
} else if (lp == 3u) {
t = 1.0 - tri;
} else if (lp != 0u) {
t = tri;
}
let m = clamp(P.f[0].w, 0.01, 0.99);
return pow(max(t, 0.0), log(0.5) / log(m));
}
// Glow bright pass. u[0].x = A & B colours; u[0].y = based on alpha; f[0].x = threshold;
// f[1] = colour A; f[2] = colour B.
@compute @workgroup_size(16, 16)
fn glow_bright(@builtin(global_invocation_id) gid: vec3<u32>) {
let dims = out_dims();
@@ -341,10 +363,13 @@ fn glow_bright(@builtin(global_invocation_id) gid: vec3<u32>) {
return;
}
let thr = P.f[0].x;
let l = luminance(px.xyz / a);
var l = luminance(px.xyz / a);
if (P.u[0].y != 0u) {
l = a;
}
let k = clamp((l - thr) / max(1.0 - thr, 1e-3), 0.0, 1.0);
if (P.u[0].x != 0u) {
let t = clamp(l, 0.0, 1.0);
let t = glow_ab_t(l);
let ca = P.f[1].xyz;
let cb = P.f[2].xyz;
let c = ca + (cb - ca) * t;
@@ -354,7 +379,8 @@ fn glow_bright(@builtin(global_invocation_id) gid: vec3<u32>) {
}
}
// Glow composite: original `src` with the blurred glow `aux`. u[0].x = behind; f[0].x = intensity.
// Glow composite: original `src` with the blurred glow `aux`. u[0].x = 0 on top, 1 behind,
// 2 none (glow only); f[0].x = intensity.
@compute @workgroup_size(16, 16)
fn glow_combine(@builtin(global_invocation_id) gid: vec3<u32>) {
let dims = out_dims();
@@ -364,7 +390,9 @@ fn glow_combine(@builtin(global_invocation_id) gid: vec3<u32>) {
}
var o = textureLoad(src, p, 0);
let g = textureLoad(aux, p, 0) * P.f[0].x;
if (P.u[0].x != 0u) {
if (P.u[0].x == 2u) {
o = g;
} else if (P.u[0].x == 1u) {
let k = 1.0 - o.w;
o = o + g * k;
} else {
@@ -518,8 +546,9 @@ fn straight(px: vec4<f32>) -> vec3<f32> {
// 8 Curves f[0] = data offsets (rgb, red, green, blue), f[1].x = alpha (−1 = none)
// 9 Gradient Ramp f[0] = (sx, sy, ex, ey); f[1] = start colour; f[2] = end colour;
// f[3] = (len2, scatter, blend); u[0] = (_, radial, seed)
// 10 Fractal Noise f[0] = (contrast, brightness, scale, frac); f[1] = (ox, oy, sr, cr);
// f[2] = (evo, blend, opacity); u[0] = (_, kind, n_oct, seed); u[1].x = invert
// 10 Fractal Noise f[0] = (contrast, brightness, scale x, frac); f[1] = (ox, oy, sr, cr);
// f[2] = (evo, blend, opacity, scale y); f[3] = (sub influence, 1 / sub scaling);
// u[0] = (_, kind, n_oct, seed); u[1] = (invert, overflow, normal blend)
@compute @workgroup_size(16, 16)
fn pointwise(@builtin(global_invocation_id) gid: vec3<u32>) {
let dims = out_dims();
@@ -546,7 +575,26 @@ fn pointwise(@builtin(global_invocation_id) gid: vec3<u32>) {
let c = straight(px);
let br = P.f[0].x;
let k = P.f[0].y;
px = vec4<f32>(max((c + br - 0.5) * k + 0.5, vec3<f32>(0.0)) * a, a);
let ct = P.f[0].z;
if (P.u[0].y == 1u) {
px = vec4<f32>(max((c + br - 0.5) * k + 0.5, vec3<f32>(0.0)) * a, a);
} else if (P.u[0].y == 2u) {
// Brightness & Contrast tone curves (effects crate bc_modern).
var o = c;
let pb = exp2(-br);
let g = exp2(2.0 * ct);
for (var i = 0; i < 3; i++) {
if (c[i] < 1.0) {
let v = powz(max(c[i], 0.0), pb);
if (v < 0.5) {
o[i] = 0.5 * powz(2.0 * v, g);
} else {
o[i] = 1.0 - 0.5 * powz(max(2.0 - 2.0 * v, 0.0), g);
}
}
}
px = vec4<f32>(o * a, a);
}
}
}
case 3u: {
@@ -600,7 +648,10 @@ fn pointwise(@builtin(global_invocation_id) gid: vec3<u32>) {
for (var i = 0; i < 3; i++) {
var t = (c[i] - ib) / max(iw - ib, 1e-6);
if (P.u[0].y != 0u) {
t = clamp(t, 0.0, 1.0);
t = max(t, 0.0);
}
if (P.u[0].z != 0u) {
t = min(t, 1.0);
}
t = powz(max(t, 0.0), 1.0 / g);
o[i] = ob + (ow - ob) * t;
@@ -613,9 +664,15 @@ fn pointwise(@builtin(global_invocation_id) gid: vec3<u32>) {
let c = straight(px);
var o = vec3<f32>(0.0);
for (var i = 0; i < 3; i++) {
let lin = srgb_to_linear(max(c[i], 0.0));
let v = powz(max(lin * P.f[0].x + P.f[0].y, 0.0), 1.0 / P.f[0].z);
o[i] = linear_to_srgb(v);
// Per-channel gain, offset, gamma; negative values are mirrored.
if (P.u[0].y != 0u) {
let x = c[i] * P.f[0][i] + P.f[1][i];
o[i] = sign(x) * powz(abs(x), 1.0 / P.f[2][i]);
} else {
let lin = srgb_to_linear(max(c[i], 0.0));
let x = lin * P.f[0][i] + P.f[1][i];
o[i] = sign(x) * linear_to_srgb(powz(abs(x), 1.0 / P.f[2][i]));
}
}
px = vec4<f32>(o * a, a);
}
@@ -706,6 +763,9 @@ fn pointwise(@builtin(global_invocation_id) gid: vec3<u32>) {
let contrast = P.f[0].x;
let brightness = P.f[0].y;
let scale = P.f[0].z;
let scale_y = P.f[2].w;
let infl = P.f[3].x;
let fmul = P.f[3].y;
let frac = P.f[0].w;
let ox = P.f[1].x;
let oy = P.f[1].y;
@@ -718,7 +778,7 @@ fn pointwise(@builtin(global_invocation_id) gid: vec3<u32>) {
let dx = f32(p.x) + 0.5 - ox;
let dy = f32(p.y) + 0.5 - oy;
let u = (dx * cr + dy * sr) / scale;
let v = (-dx * sr + dy * cr) / scale;
let v = (-dx * sr + dy * cr) / scale_y;
var sum = 0.0;
var amp = 1.0;
var norm = 0.0;
@@ -729,20 +789,40 @@ fn pointwise(@builtin(global_invocation_id) gid: vec3<u32>) {
w = frac;
}
var n = value_noise(u * f, v * f, evo + f32(o) * 7.31, seed + o);
let s = n * 2.0 - 1.0;
if (kind == 1u) {
n = 1.0 - abs(n * 2.0 - 1.0);
n = 1.0 - s * s;
} else if (kind == 2u) {
n = 1.0 - abs(s);
}
sum += n * amp * w;
norm += amp * w;
amp *= 0.5;
f *= 2.0;
amp *= infl;
f *= fmul;
}
var val = sum / max(norm, 1e-6);
val = clamp((val - 0.5) * contrast + 0.5 + brightness, 0.0, 1.0);
val = (val - 0.5) * contrast + 0.5 + brightness;
// Overflow: Clip, Soft Clamp, Wrap Back, Allow HDR Results.
let ov = P.u[1].y;
if (ov == 0u) {
val = clamp(val, 0.0, 1.0);
} else if (ov == 1u) {
val = clamp(0.5 + 0.5 * tanh(2.0 * (val - 0.5)) / tanh(1.0), 0.0, 1.0);
} else if (ov == 2u) {
let t = val - 2.0 * floor(val / 2.0);
val = select(t, 2.0 - t, t > 1.0);
}
if (P.u[1].x != 0u) {
val = 1.0 - val;
}
px = put(px, vec3<f32>(val), P.f[2].z, P.f[2].y);
let op = P.f[2].z;
if (P.u[1].z == 1u) {
// Normal: the noise over the layer at Opacity, then Blend With Original.
let over = vec4<f32>(vec3<f32>(val) * op, op) + px * (1.0 - op);
px = px * P.f[2].y + over * (1.0 - P.f[2].y);
} else {
px = put(px, vec3<f32>(val), op, P.f[2].y);
}
}
default: {}
}
+43 -4
View File
@@ -434,8 +434,22 @@ fn gpu_effects_match_cpu() {
effect_case("ec.blur.directional", &[("direction", n(30.0)), ("length", n(12.0))]);
effect_case("ec.stylize.glow", &[("threshold", n(40.0)), ("radius", n(14.0)), ("intensity", n(1.5))]);
effect_case("ec.stylize.glow", &[("colors", Value::Enum(1)), ("operation", Value::Enum(1)), ("colorA", c(1.0, 0.8, 0.1))]);
effect_case(
"ec.stylize.glow",
&[
("colors", Value::Enum(1)),
("colorLooping", Value::Enum(3)),
("colorLoops", n(2.5)),
("colorPhase", n(40.0)),
("abMidpoint", n(30.0)),
("glowDimensions", Value::Enum(1)),
],
);
effect_case("ec.stylize.glow", &[("based", Value::Enum(0)), ("operation", Value::Enum(2)), ("threshold", n(20.0))]);
effect_case("ec.color.levels", &[("inBlack", n(0.1)), ("inWhite", n(0.8)), ("gamma", n(1.6)), ("outBlack", n(0.05))]);
effect_case("ec.color.levels", &[("inWhite", n(0.5)), ("noClip", Value::Bool(true))]);
effect_case("ec.color.levels", &[("inBlack", n(0.2)), ("inWhite", n(0.6)), ("clipToOutputBlack", Value::Enum(0))]);
effect_case("ec.color.levels", &[("inBlack", n(0.2)), ("inWhite", n(0.6)), ("clipToOutputWhite", Value::Enum(0))]);
effect_case(
"ec.color.curves",
&[("rgb", Value::Str("0,0 0.3,0.45 1,1".into())), ("red", Value::Str("0,0.1 1,0.9".into())), ("alpha", Value::Str("0,0 0.5,0.7 1,1".into()))],
@@ -450,15 +464,40 @@ fn gpu_effects_match_cpu() {
effect_case("ec.noise.fractal", &[]);
effect_case(
"ec.noise.fractal",
&[("fractalType", Value::Enum(1)), ("complexity", n(4.5)), ("rotation", n(30.0)), ("evolution", n(90.0)), ("scale", n(40.0)), ("blend", n(30.0))],
&[
("fractalType", Value::Enum(1)),
("complexity", n(4.5)),
("transform/rotation", n(30.0)),
("evolution", n(90.0)),
("transform/scale", n(40.0)),
("blend", n(30.0)),
],
);
effect_case(
"ec.noise.fractal",
&[
("fractalType", Value::Enum(2)),
("overflow", Value::Enum(1)),
("contrast", n(250.0)),
("transform/uniformScaling", Value::Bool(false)),
("transform/scaleWidth", n(60.0)),
("transform/scaleHeight", n(25.0)),
("subSettings/subInfluence", n(40.0)),
("subSettings/subScaling", n(70.0)),
("opacity", n(60.0)),
],
);
effect_case("ec.noise.fractal", &[("overflow", Value::Enum(2)), ("contrast", n(300.0)), ("blendingMode", Value::Enum(0)), ("opacity", n(50.0))]);
effect_case("ec.perspective.dropshadow", &[("distance", n(6.0)), ("softness", n(8.0)), ("opacity", n(80.0))]);
effect_case("ec.perspective.dropshadow", &[("shadowOnly", Value::Bool(true)), ("color", c(0.0, 0.2, 0.6))]);
effect_case("ec.color.brightnesscontrast", &[("brightness", n(20.0)), ("contrast", n(35.0))]);
effect_case("ec.color.brightnesscontrast", &[("brightness", n(-10.0)), ("contrast", n(-40.0))]);
effect_case("ec.color.exposure", &[("exposure", n(1.3)), ("offset", n(0.02)), ("gamma", n(1.2))]);
effect_case("ec.color.brightnesscontrast", &[("brightness", n(20.0)), ("contrast", n(35.0)), ("useLegacy", Value::Bool(true))]);
effect_case("ec.color.exposure", &[("master/exposure", n(1.3)), ("master/offset", n(0.02)), ("master/gamma", n(1.2))]);
effect_case("ec.color.exposure", &[("channels", Value::Enum(1)), ("red/redExposure", n(0.8)), ("green/greenOffset", n(-0.05)), ("blue/blueGamma", n(1.6))]);
effect_case("ec.color.exposure", &[("master/exposure", n(0.5)), ("master/offset", n(-0.03)), ("bypassLinearLight", Value::Bool(true))]);
effect_case("ec.channel.invert", &[]);
effect_case("ec.channel.invert", &[("channel", Value::Enum(4)), ("blend", n(30.0))]);
effect_case("ec.channel.invert", &[("channel", Value::Enum(effectcraft_effects::INVERT_ALPHA)), ("blend", n(30.0))]);
effect_case("ec.channel.invert", &[("channel", Value::Enum(2))]);
effect_case("ec.distort.transform", &[("rotation", n(20.0)), ("scaleHeight", n(80.0)), ("skew", n(10.0)), ("opacity", n(70.0))]);
}
@@ -470,7 +509,7 @@ fn effect_chains_and_mixed_stacks() {
for depth in [BitDepth::Bpc8, BitDepth::Bpc32] {
let mut s = Scene::new(depth);
let mut l = s.footage(80, 50);
s.effect(&mut l, "ec.color.exposure", &[("exposure", n(0.7))]);
s.effect(&mut l, "ec.color.exposure", &[("master/exposure", n(0.7))]);
s.effect(&mut l, "ec.blur.gaussian", &[("blurriness", n(5.0))]);
s.effect(&mut l, "ec.stylize.posterize", &[]);
s.effect(&mut l, "ec.stylize.glow", &[]);