M13.13: GPU Circle, Ellipse, Iris Wipe, Bevel Alpha, Bevel Edges, Gaussian Blur (Legacy)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Brandon ThomasandClaude Opus 5.5 committed 2026-10-03 10:53:00 -04:00
1 parent ea292a6e81
commit 750868244a
9 files changed
+486 -11

No files matched your search

+7
View File
@@ -922,6 +922,13 @@ pub const GPU_EFFECTS: &[&str] = &[
"ec.distort.reshape",
"ec.stylize.coloremboss",
"ec.stylize.cartoon",
// effectcraft-gpu fx_extra (shapes, bevels)
"ec.generate.circle",
"ec.generate.ellipse",
"ec.transition.iriswipe",
"ec.perspective.bevelalpha",
"ec.perspective.beveledges",
"ec.obsolete.gaussianlegacy",
];
/// Effects whose output depends on [`EffectCtx::time`] directly (not only through animated
+2
View File
@@ -313,6 +313,7 @@ impl GpuContext {
include_str!("shaders/fx_noise.wgsl"),
include_str!("shaders/fx_tone.wgsl"),
include_str!("shaders/fx_warp.wgsl"),
include_str!("shaders/fx_extra.wgsl"),
]
.concat();
let module =
@@ -371,6 +372,7 @@ impl GpuContext {
.chain(crate::fx_noise::KERNELS)
.chain(crate::fx_tone::KERNELS)
.chain(crate::fx_warp::KERNELS)
.chain(crate::fx_extra::KERNELS)
.map(|e| (e, &layout))
.chain(EXT_ENTRIES.iter().map(|e| (e, &layout_ext)))
.map(|(e, layout)| {
+1
View File
@@ -132,6 +132,7 @@ fn apply(e: &mut Enc, id: &str, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
_ if crate::fx_noise::IDS.contains(&id) => crate::fx_noise::apply(e, id, ctx, b),
_ if crate::fx_tone::IDS.contains(&id) => crate::fx_tone::apply(e, id, ctx, b),
_ if crate::fx_warp::IDS.contains(&id) => crate::fx_warp::apply(e, id, ctx, b),
_ if crate::fx_extra::IDS.contains(&id) => crate::fx_extra::apply(e, id, ctx, b),
_ => pointwise(e, id, ctx, b),
}
}
+196
View File
@@ -0,0 +1,196 @@
//! GPU effects, shapes and bevels (kernels in `shaders/fx_extra.wgsl`, every entry point
//! prefixed `fxe_`): Circle, Ellipse, Iris Wipe, Bevel Alpha, Bevel Edges and Gaussian Blur
//! (Legacy), with the CPU effects' exact steps. Radii, feathers, light directions and Iris
//! Wipe's polygon are computed on the CPU in f64 as the CPU effect does; the kernels work
//! relative to the shape's centre.
use effectcraft_effects::EffectCtx;
use crate::context::{Enc, Params};
use crate::effects::{GBuf, gaussian_blur};
/// Compute entry points in `fx_extra.wgsl`.
pub(crate) const KERNELS: &[&str] = &["fxe_circle", "fxe_ellipse", "fxe_iris", "fxe_alpha", "fxe_bevel_alpha", "fxe_bevel_edges"];
/// Effect ids implemented here.
pub(crate) const IDS: &[&str] = &[
"ec.generate.circle",
"ec.generate.ellipse",
"ec.transition.iriswipe",
"ec.perspective.bevelalpha",
"ec.perspective.beveledges",
"ec.obsolete.gaussianlegacy",
];
/// 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> {
match id {
"ec.generate.circle" => circle(e, ctx, b),
"ec.generate.ellipse" => ellipse(e, ctx, b),
"ec.transition.iriswipe" => iris_wipe(e, ctx, b),
"ec.perspective.bevelalpha" => bevel_alpha(e, ctx, b),
"ec.perspective.beveledges" => bevel_edges(e, ctx, b),
"ec.obsolete.gaussianlegacy" => gaussian_legacy(e, ctx, b),
_ => None,
}
}
/// A same-size per-pixel kernel over the buffer.
fn run(e: &mut Enc, entry: &str, p: &Params, mut b: GBuf, aux: Option<&crate::context::GpuImage>, data: Option<&wgpu::Buffer>) -> Option<GBuf> {
let out = e.scratch(b.img.width, b.img.height);
e.pixels(entry, p, &b.img, aux, &out, data);
b.img = out;
Some(b)
}
fn f4(v: [f64; 4]) -> [f32; 4] {
v.map(|x| x as f32)
}
// ---------------------------------------------------------------- Circle (generate::circle)
fn circle(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
let pr = ctx.params;
let c = b.to_px(pr.v2("center"));
let r = pr.f("radius") * b.scale;
let thick = pr.f("thickness").max(0.0);
let mut f_out = pr.f("feather/feather").max(0.0) * b.scale;
let mut f_in = pr.f("feather/featherInner").max(0.0) * b.scale;
let inner = match pr.e("edge") {
1 => Some(pr.f("edgeRadius") * b.scale),
2 => Some(r - thick * b.scale),
3 => Some(r - thick / 100.0 * r),
4 => {
f_out = pr.f("feather/feather").max(0.0) / 100.0 * r;
f_in = pr.f("feather/featherInner").max(0.0) / 100.0 * r;
Some(r - thick / 100.0 * r)
}
_ => None,
};
let (ro, ri) = match inner {
Some(i) => (r.max(i), Some(r.min(i).max(0.0))),
None => (r, None),
};
let (f_out, f_in) = (f_out.max(0.5), f_in.max(0.5));
let color = pr.color("color");
let mut p = Params::default();
p.u[0] = [crate::fx_generate::gen_mode(pr.e("blendingMode")), ri.is_some() as u32, pr.b("invert") as u32, 0];
p.f[0] = f4([c.0, c.1, ro, ri.unwrap_or(0.0)]);
p.f[1] = [f_out as f32, f_in as f32, pr.f("opacity") as f32 / 100.0, 0.0];
p.f[2] = [color[0], color[1], color[2], 0.0];
run(e, "fxe_circle", &p, b, None, None)
}
// ---------------------------------------------------------------- Ellipse (generate2::ellipse)
fn ellipse(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
let pr = ctx.params;
let c = b.to_px(pr.v2("center"));
let rx = (pr.f("width") * b.scale * 0.5).max(0.5);
let ry = (pr.f("height") * b.scale * 0.5).max(0.5);
let half = (pr.f("thickness") * b.scale * 0.5).max(0.25);
let soft = (pr.f("softness") / 100.0).clamp(0.0, 1.0);
let (inside, outside) = (pr.color("insideColor"), pr.color("outsideColor"));
let mut p = Params::default();
p.u[0][1] = pr.b("compositeOnOriginal") as u32;
p.f[0] = f4([c.0, c.1, rx, ry]);
p.f[1] = [half as f32, soft as f32, 0.0, 0.0];
p.f[2] = [inside[0], inside[1], inside[2], 0.0];
p.f[3] = [outside[0], outside[1], outside[2], 0.0];
run(e, "fxe_ellipse", &p, b, None, None)
}
// ---------------------------------------------------------------- Iris Wipe (transition::iris_wipe)
fn iris_wipe(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
let pr = ctx.params;
let outer = pr.f("outerRadius").max(0.0) * b.scale;
if outer <= 0.0 {
return Some(b);
}
let n = pr.f("points").round().clamp(3.0, 64.0) as usize;
let star = pr.b("useInnerRadius");
let inner = if star { pr.f("innerRadius").max(0.0) * b.scale } else { outer };
let rot = pr.f("rotation").to_radians();
let c = b.to_px(pr.v2("center"));
let m = if star { n * 2 } else { n };
let step = std::f64::consts::TAU / m as f64;
let verts: Vec<f32> = (0..m)
.flat_map(|i| {
let r = if star && i % 2 == 1 { inner } else { outer };
let a = rot + i as f64 * step;
[(r * a.sin()) as f32, (-r * a.cos()) as f32]
})
.collect();
let buf = e.data(&verts);
let mut p = Params::default();
p.u[0][1] = m as u32;
p.f[0] = f4([c.0, c.1, rot, step]);
p.f[1] = f4([pr.f("feather") * b.scale, outer.min(inner), outer, 0.0]);
run(e, "fxe_iris", &p, b, None, Some(&buf))
}
// ---------------------------------------------------------------- Bevels (perspective.rs)
/// perspective::light_dir.
fn light_dir(angle_deg: f64, elevation_deg: f64) -> [f64; 3] {
let (a, el) = (angle_deg.to_radians(), elevation_deg.to_radians());
let v = [a.sin() * el.cos(), -a.cos() * el.cos(), el.sin()];
let l = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt().max(1e-12);
v.map(|x| x / l)
}
fn bevel_alpha(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
let pr = ctx.params;
let t = pr.f("edgeThickness") * b.scale;
let inten = pr.f("lightIntensity") as f32;
if t <= 0.0 || inten == 0.0 {
return Some(b);
}
let light = pr.color("lightColor");
let l = light_dir(pr.f("lightAngle"), 45.0);
// The alpha plane's Gaussian (util::gauss_plane: box passes, edges repeated); kernels read x.
let alpha = e.scratch(b.img.width, b.img.height);
e.pixels("fxe_alpha", &Params::default(), &b.img, None, &alpha, None);
let h = gaussian_blur(e, &alpha, t * 0.5, t * 0.5, true);
let mut p = Params::default();
p.f[0] = f4([l[0], l[1], l[2], t * 1.25]);
p.f[1] = [light[0], light[1], light[2], inten];
run(e, "fxe_bevel_alpha", &p, b, Some(&h), None)
}
fn bevel_edges(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
let pr = ctx.params;
let (x0, y0, w, h) = (b.offset[0], b.offset[1], ctx.layer_size[0] * b.scale, ctx.layer_size[1] * b.scale);
let t = pr.f("edgeThickness").clamp(0.0, 0.5) * w.min(h);
let inten = pr.f("lightIntensity") as f32;
if t <= 0.0 || inten == 0.0 {
return Some(b);
}
let light = pr.color("lightColor");
let l = light_dir(pr.f("lightAngle"), 45.0);
let mut p = Params::default();
p.f[0] = f4([l[0], l[1], l[2], t]);
p.f[1] = [light[0], light[1], light[2], inten];
p.f[2] = f4([x0, y0, w, h]);
run(e, "fxe_bevel_edges", &p, b, None, None)
}
// ---------------------------------------------------------------- Gaussian Blur (Legacy) (obsolete.rs)
fn gaussian_legacy(e: &mut Enc, ctx: &EffectCtx, mut b: GBuf) -> Option<GBuf> {
let s = ctx.params.f("blurriness").max(0.0) * 0.5 * b.scale;
if s <= 0.0 {
return Some(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(e, (s * 3.0).ceil() as u32)?;
}
b.img = gaussian_blur(e, &b.img, s * kx, s * ky, ctx.adjustment);
Some(b)
}
+1 -1
View File
@@ -83,7 +83,7 @@ fn hl(v: f64) -> [f32; 2] {
}
/// Generate blending mode index → `BlendMode::ALL` index (255 = None: replace).
fn gen_mode(i: u32) -> u32 {
pub(crate) fn gen_mode(i: u32) -> u32 {
match effectcraft_effects::gen_mode(i) {
None => 255,
Some(m) => BlendMode::ALL.iter().position(|&x| x == m).unwrap_or(0) as u32,
+15 -3
View File
@@ -17,9 +17,18 @@
//! GPU effects ([`effectcraft_effects::GPU_EFFECTS`]) run as compute kernels with the CPU
//! effect's exact steps (padding, box-blur radii, parameter conversions); chains of them are
//! uploaded and read back once. Each family lives in its own module with its own WGSL file
//! (`fx_color`, `fx_distort`, `fx_generate`, `fx_key`, `fx_noise`, `fx_stylize`, `fx_tone`);
//! settings a kernel cannot match fall back to the CPU (`catalog::gpu_supported`, or `None`
//! from the family's `apply`).
//! (`fx_color`, `fx_distort`, `fx_extra`, `fx_generate`, `fx_key`, `fx_noise`, `fx_stylize`,
//! `fx_tone`, `fx_warp`); settings a kernel cannot match fall back to the CPU
//! (`catalog::gpu_supported`, or `None` from the family's `apply`).
//!
//! Working textures come from a pool (`context::Pool`): a frame allocates several full-frame
//! RGBA f32 images per layer, and creating and zeroing those cost more than compositing a small
//! layer. A released texture is reused once every encoder that could still read it has been
//! submitted. 8/16 bpc quantisation after each layer is fused into that layer's composite.
//! [`Backend::Auto`](effectcraft_render::Backend) renders each comp on whichever compositor
//! measured faster for it ([`effectcraft_render::AutoPick`], kept by [`Gpu`]): a light comp
//! that the CPU composites in a millisecond stays there rather than paying for a full-frame
//! readback.
//!
//! GPU particles (`particles`): the stepped particle effects hand their simulation to
//! [`effectcraft_effects::psim::ParticleSim`], implemented here with one invocation per particle
@@ -37,6 +46,7 @@ mod context;
mod effects;
mod fx_color;
mod fx_distort;
mod fx_extra;
mod fx_generate;
mod fx_key;
mod fx_noise;
@@ -186,6 +196,8 @@ mod tests_fx_color;
#[cfg(test)]
mod tests_fx_distort;
#[cfg(test)]
mod tests_fx_extra;
#[cfg(test)]
mod tests_fx_generate;
#[cfg(test)]
mod tests_fx_key;
+206
View File
@@ -0,0 +1,206 @@
// GPU effects (shapes and bevels): see src/fx_extra.rs. Circle, Ellipse, Iris Wipe, Bevel Alpha
// and Bevel Edges, each mirroring the CPU effect operation for operation. Coordinates are pixel
// centres relative to the shape's centre (or the layer's corner), so f32 keeps its precision.
fn fxe_inside(p: vec2<i32>) -> bool {
let d = out_dims();
return p.x < d.x && p.y < d.y;
}
fn fxe_rel(p: vec2<i32>, c: vec2<f32>) -> vec2<f32> {
return vec2<f32>(f32(p.x) + 0.5, f32(p.y) + 0.5) - c;
}
// ---------------------------------------------------------------- Circle (generate::circle)
// u[0] = (gen mode, has inner, invert); f[0] = (cx, cy, outer radius, inner radius);
// f[1] = (outer feather, inner feather, opacity); f[2] = colour.
@compute @workgroup_size(16, 16)
fn fxe_circle(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = vec2<i32>(gid.xy);
if (!fxe_inside(p)) {
return;
}
let d = length(fxe_rel(p, P.f[0].xy));
var cov = clamp((P.f[0].z - d) / P.f[1].x + 0.5, 0.0, 1.0);
if (P.u[0].y != 0u) {
cov *= clamp((d - P.f[0].w) / P.f[1].y + 0.5, 0.0, 1.0);
}
if (P.u[0].z != 0u) {
cov = 1.0 - cov;
}
let a = cov * P.f[1].z;
textureStore(out, p, gen_blend(textureLoad(src, p, 0), vec4<f32>(P.f[2].xyz * a, a), P.u[0].x));
}
// ---------------------------------------------------------------- Ellipse (generate2::ellipse)
// u[0].y = composite on original; f[0] = (cx, cy, rx, ry); f[1] = (half thickness, softness);
// f[2] = inside colour; f[3] = outside colour.
fn fxe_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);
}
@compute @workgroup_size(16, 16)
fn fxe_ellipse(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = vec2<i32>(gid.xy);
if (!fxe_inside(p)) {
return;
}
let d = fxe_rel(p, P.f[0].xy);
let r = P.f[0].zw;
let q = length(d / r);
var dist = min(r.x, r.y);
if (q >= 1e-9) {
let g = d / (r * r) / q;
dist = abs((q - 1.0) / max(length(g), 1e-9));
}
let half = P.f[1].x;
let soft = P.f[1].y;
let t = dist / half;
var a = clamp(half - dist + 0.5, 0.0, 1.0);
if (soft > 1e-3) {
a = 1.0 - fxe_smoothstep(1.0 - soft, 1.0, t);
}
let k = fxe_smoothstep(0.0, 1.0, t);
let cc = P.f[2].xyz + (P.f[3].xyz - P.f[2].xyz) * k;
let g4 = vec4<f32>(cc * a, a);
if (P.u[0].y != 0u) {
let o = textureLoad(src, p, 0);
textureStore(out, p, g4 + o * (1.0 - g4.w));
} else {
textureStore(out, p, g4);
}
}
// ---------------------------------------------------------------- Iris Wipe (transition::iris_wipe)
// data = polygon vertices relative to the centre; u[0] = (_, vertex count);
// f[0] = (cx, cy, rotation, angle step); f[1] = (feather, radius at the centre, outer radius).
fn fxe_cross(a: vec2<f32>, b: vec2<f32>) -> f32 {
return a.x * b.y - a.y * b.x;
}
@compute @workgroup_size(16, 16)
fn fxe_iris(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = vec2<i32>(gid.xy);
if (!fxe_inside(p)) {
return;
}
let d = fxe_rel(p, P.f[0].xy);
let r = length(d);
let m = P.u[0].y;
let rot = P.f[0].z;
let step = P.f[0].w;
var rb = P.f[1].y;
if (r >= 1e-9) {
let tau = 6.2831855;
let a0 = atan2(d.x, -d.y) - rot;
let a = a0 - floor(a0 / tau) * tau;
let i = min(u32(floor(a / step)), m - 1u);
let vi = vec2<f32>(data[2u * i], data[2u * i + 1u]);
let j = (i + 1u) % m;
let vj = vec2<f32>(data[2u * j], data[2u * j + 1u]);
let e = vj - vi;
let den = fxe_cross(d / r, e);
rb = select(fxe_cross(vi, e) / den, P.f[1].z, abs(den) < 1e-12);
}
let feather = P.f[1].x;
var k = select(0.0, 1.0, r >= rb);
if (feather > 0.0) {
k = clamp((r - rb) / feather + 0.5, 0.0, 1.0);
}
textureStore(out, p, textureLoad(src, p, 0) * k);
}
// ---------------------------------------------------------------- Bevels (perspective.rs)
// perspective::apply_shade.
fn fxe_shade(px: vec4<f32>, s: f32, light: vec3<f32>) -> vec4<f32> {
if (s > 0.0) {
return vec4<f32>(px.xyz + light * s * px.w, px.w);
}
if (s < 0.0) {
return vec4<f32>(px.xyz * max(1.0 + s, 0.0), px.w);
}
return px;
}
// Plane::alpha (in x).
@compute @workgroup_size(16, 16)
fn fxe_alpha(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = vec2<i32>(gid.xy);
if (!fxe_inside(p)) {
return;
}
textureStore(out, p, vec4<f32>(textureLoad(src, p, 0).w, 0.0, 0.0, 0.0));
}
// Bevel Alpha: src = the layer, aux = its blurred alpha (x). f[0] = (light direction, normal
// scale k); f[1] = (light colour, intensity).
@compute @workgroup_size(16, 16)
fn fxe_bevel_alpha(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = vec2<i32>(gid.xy);
if (!fxe_inside(p)) {
return;
}
let px = textureLoad(src, p, 0);
if (px.w <= 0.0) {
textureStore(out, p, px);
return;
}
let dx = (tex_get_clamped(aux, p.x + 1, p.y).x - tex_get_clamped(aux, p.x - 1, p.y).x) * 0.5;
let dy = (tex_get_clamped(aux, p.x, p.y + 1).x - tex_get_clamped(aux, p.x, p.y - 1).x) * 0.5;
let k = P.f[0].w;
let n = normalize(vec3<f32>(-dx * k, -dy * k, 1.0));
let l = P.f[0].xyz;
let s = (dot(n, l) - l.z) * P.f[1].w * 2.0;
textureStore(out, p, fxe_shade(px, s, P.f[1].xyz));
}
// Bevel Edges: f[0] = (light direction, thickness); f[1] = (light colour, intensity);
// f[2] = layer rectangle (x0, y0, w, h).
@compute @workgroup_size(16, 16)
fn fxe_bevel_edges(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = vec2<i32>(gid.xy);
if (!fxe_inside(p)) {
return;
}
let px = textureLoad(src, p, 0);
let rc = P.f[2];
let q = vec2<f32>(f32(p.x) + 0.5, f32(p.y) + 0.5);
let d = vec4<f32>(q.x - rc.x, rc.x + rc.z - q.x, q.y - rc.y, rc.y + rc.w - q.y);
if (d.x < 0.0 || d.y < 0.0 || d.z < 0.0 || d.w < 0.0) {
textureStore(out, p, px);
return;
}
var mi = 0;
var mv = d.x;
for (var i = 1; i < 4; i++) {
if (d[i] < mv) {
mv = d[i];
mi = i;
}
}
if (mv >= P.f[0].w) {
textureStore(out, p, px);
return;
}
var n2 = vec2<f32>(-1.0, 0.0);
if (mi == 1) {
n2 = vec2<f32>(1.0, 0.0);
} else if (mi == 2) {
n2 = vec2<f32>(0.0, -1.0);
} else if (mi == 3) {
n2 = vec2<f32>(0.0, 1.0);
}
let tilt = 0.70710677;
let n = vec3<f32>(n2 * tilt, tilt);
let l = P.f[0].xyz;
let s = (dot(n, l) - l.z) * P.f[1].w * 2.0;
textureStore(out, p, fxe_shade(px, s, P.f[1].xyz));
}
+51
View File
@@ -0,0 +1,51 @@
//! GPU shape and bevel effects (Circle, Ellipse, Iris Wipe, Bevel Alpha, Bevel Edges, Gaussian
//! Blur (Legacy)) 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)
}
fn pt(x: f64, y: f64) -> Value {
Value::Vec2([x, y])
}
#[test]
fn circle_and_ellipse() {
effect_case("ec.generate.circle", &[("center", pt(30.0, 20.0)), ("radius", n(15.0))]);
effect_case("ec.generate.circle", &[("radius", n(18.0)), ("edge", e(2)), ("thickness", n(5.0)), ("feather/feather", n(3.0)), ("blendingMode", e(3))]);
effect_case(
"ec.generate.circle",
&[("radius", n(20.0)), ("edge", e(4)), ("thickness", n(30.0)), ("feather/featherInner", n(10.0)), ("invert", on()), ("opacity", n(60.0))],
);
effect_case("ec.generate.circle", &[("radius", n(12.0)), ("edge", e(1)), ("edgeRadius", n(25.0)), ("color", c(0.2, 0.8, 0.4))]);
effect_case("ec.generate.ellipse", &[("center", pt(35.0, 22.0)), ("width", n(50.0)), ("height", n(30.0)), ("thickness", n(6.0))]);
effect_case("ec.generate.ellipse", &[("width", n(40.0)), ("height", n(60.0)), ("softness", n(40.0)), ("compositeOnOriginal", on())]);
}
#[test]
fn iris_wipe() {
effect_case("ec.transition.iriswipe", &[("outerRadius", n(20.0)), ("center", pt(35.0, 22.0))]);
effect_case(
"ec.transition.iriswipe",
&[("outerRadius", n(25.0)), ("points", n(5.0)), ("useInnerRadius", on()), ("innerRadius", n(10.0)), ("rotation", n(20.0)), ("feather", n(4.0))],
);
}
#[test]
fn bevels_and_legacy_gaussian() {
effect_case("ec.perspective.bevelalpha", &[("edgeThickness", n(4.0)), ("lightIntensity", n(0.6))]);
effect_case("ec.perspective.bevelalpha", &[("edgeThickness", n(2.0)), ("lightAngle", n(200.0)), ("lightColor", c(1.0, 0.8, 0.5))]);
effect_case("ec.perspective.beveledges", &[("edgeThickness", n(0.1)), ("lightIntensity", n(0.5))]);
effect_case("ec.perspective.beveledges", &[("edgeThickness", n(0.3)), ("lightAngle", n(130.0))]);
effect_case("ec.obsolete.gaussianlegacy", &[("blurriness", n(7.0))]);
effect_case("ec.obsolete.gaussianlegacy", &[("blurriness", n(5.0)), ("blurDimensions", e(2))]);
}
+7 -7
View File
@@ -5,7 +5,7 @@
EffectCraft ships 306 effects with 3009 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**: 160 effects also run on the GPU compositor with identical results.
- **GPU**: 166 effects also run on the GPU compositor with identical results.
- **32**: 306 effects process 32-bit float (HDR, overbright) pixels without clamping.
- **Status**: 306 implemented in full, 0 partial (what is missing is listed).
@@ -200,8 +200,8 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
| CC Threads | `ec.generate.ccthreads` | 8 | | 32 | Implemented |
| Cell Pattern | `ec.generate.cellpattern` | 14 | GPU | 32 | Implemented |
| Checkerboard | `ec.generate.checkerboard` | 10 | GPU | 32 | Implemented |
| Circle | `ec.generate.circle` | 11 | | 32 | Implemented |
| Ellipse | `ec.generate.ellipse` | 8 | | 32 | Implemented |
| Circle | `ec.generate.circle` | 11 | GPU | 32 | Implemented |
| Ellipse | `ec.generate.ellipse` | 8 | GPU | 32 | Implemented |
| Eyedropper Fill | `ec.generate.eyedropperfill` | 5 | | 32 | Implemented |
| Fill | `ec.generate.fill` | 7 | GPU | 32 | Implemented |
| Fractal | `ec.generate.fractal` | 21 | | 32 | Implemented |
@@ -285,7 +285,7 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
| Basic 3D | `ec.obsolete.basic3d` | 5 | | 32 | Implemented |
| Basic Text | `ec.obsolete.basictext` | 11 | | 32 | Implemented |
| Color Key | `ec.key.colorkey` | 4 | GPU | 32 | Implemented |
| Gaussian Blur (Legacy) | `ec.obsolete.gaussianlegacy` | 2 | | 32 | Implemented |
| Gaussian Blur (Legacy) | `ec.obsolete.gaussianlegacy` | 2 | GPU | 32 | Implemented |
| Lightning | `ec.obsolete.lightning` | 25 | | 32 | Implemented |
| Luma Key | `ec.key.luma` | 5 | GPU | 32 | Implemented |
| Path Text | `ec.obsolete.pathtext` | 32 | | 32 | Implemented |
@@ -304,8 +304,8 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|---|---|---:|:-:|:-:|---|
| 3D Camera Tracker | `ec.perspective.cameratracker` | 12 | | 32 | Implemented |
| 3D Glasses | `ec.perspective.3dglasses` | 8 | | 32 | Implemented |
| Bevel Alpha | `ec.perspective.bevelalpha` | 4 | | 32 | Implemented |
| Bevel Edges | `ec.perspective.beveledges` | 4 | | 32 | Implemented |
| Bevel Alpha | `ec.perspective.bevelalpha` | 4 | GPU | 32 | Implemented |
| Bevel Edges | `ec.perspective.beveledges` | 4 | GPU | 32 | Implemented |
| CC Cylinder | `ec.perspective.cccylinder` | 12 | | 32 | Implemented |
| CC Environment | `ec.perspective.ccenvironment` | 4 | | 32 | Implemented |
| CC Sphere | `ec.perspective.ccsphere` | 14 | | 32 | Implemented |
@@ -403,7 +403,7 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
| CC WarpoMatic | `ec.transition.ccwarpomatic` | 8 | | 32 | Implemented |
| Card Wipe | `ec.transition.cardwipe` | 44 | | 32 | Implemented |
| Gradient Wipe | `ec.transition.gradientwipe` | 5 | GPU | 32 | Implemented |
| Iris Wipe | `ec.transition.iriswipe` | 7 | | 32 | Implemented |
| Iris Wipe | `ec.transition.iriswipe` | 7 | GPU | 32 | Implemented |
| Linear Wipe | `ec.transition.linearwipe` | 3 | GPU | 32 | Implemented |
| Radial Wipe | `ec.transition.radialwipe` | 5 | GPU | 32 | Implemented |
| Venetian Blinds | `ec.transition.venetian` | 4 | GPU | 32 | Implemented |