Merge M13.9 gpu stylize and distort (fx_stylize)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
0fbde2f4ec
|
|
@ -88,6 +88,7 @@ const CPU_ONLY_CONTROLS: &[(&str, &[&str])] = &[
|
|||
("ec.noise.addgrain", &["viewingMode", "preset", "color/redBalance", "color/greenBalance", "color/blueBalance", "previewRegion/"]),
|
||||
("ec.blur.cameralens", &["irisProperties/diffractionFringe", "blurMap/blurMapLayer"]),
|
||||
("ec.noise.removegrain", &["temporalFiltering/enabled"]),
|
||||
("ec.distort.liquify", &["viewFreezeAreaMask", "viewMesh"]),
|
||||
];
|
||||
|
||||
/// Whether the GPU kernel of effect `id` covers the instance's current settings (see
|
||||
|
|
|
|||
|
|
@ -354,7 +354,7 @@ fn magnify(ctx: &EffectCtx, mut b: Buf) -> Buf {
|
|||
}
|
||||
|
||||
/// Magnify's Blending Mode choices after "None".
|
||||
const MAGNIFY_MODES: [BlendMode; 17] = [
|
||||
pub const MAGNIFY_MODES: [BlendMode; 17] = [
|
||||
BlendMode::Normal,
|
||||
BlendMode::Add,
|
||||
BlendMode::Multiply,
|
||||
|
|
|
|||
|
|
@ -325,7 +325,9 @@ pub fn build_mesh(strokes: &[LiquifyStroke], w: f64, h: f64, frozen: &(dyn Fn(f6
|
|||
|
||||
type MeshCache = Mutex<HashMap<u64, Arc<Mesh>>>;
|
||||
|
||||
fn cached_mesh(ctx: &EffectCtx) -> Arc<Mesh> {
|
||||
/// Liquify's displacement mesh for the instance (replayed strokes, cached; also used by the
|
||||
/// GPU kernels).
|
||||
pub fn liquify_mesh(ctx: &EffectCtx) -> Arc<Mesh> {
|
||||
use std::hash::{Hash, Hasher};
|
||||
let text = ctx.params.s("distortionMesh");
|
||||
let mask = ctx.params.f("freezeAreaMask").round() as usize;
|
||||
|
|
@ -369,7 +371,7 @@ fn liquify(ctx: &EffectCtx, mut b: Buf) -> Buf {
|
|||
if ctx.params.s("distortionMesh").trim().is_empty() && !show_freeze && !show_mesh {
|
||||
return b;
|
||||
}
|
||||
let mesh = cached_mesh(ctx);
|
||||
let mesh = liquify_mesh(ctx);
|
||||
let off = ctx.params.v2("distortionMeshOffset");
|
||||
let mesh_color = ctx.params.color("meshColor");
|
||||
let src = b.img.clone();
|
||||
|
|
|
|||
|
|
@ -72,7 +72,8 @@ pub use color_fx::{
|
|||
};
|
||||
pub use color2::{Curve, auto_correct_settings, equalize_tables, luma_clip_points, shadow_highlight_amounts};
|
||||
pub use distort::transform_shutter;
|
||||
pub use distort2::parse_mesh;
|
||||
pub use distort2::{MAGNIFY_MODES, parse_mesh};
|
||||
pub use distort4::liquify_mesh;
|
||||
use effectcraft_keyframe::Value;
|
||||
use effectcraft_project::build::Ids;
|
||||
use effectcraft_project::{GroupKind, ParamUi, PropGroup, Property};
|
||||
|
|
@ -80,6 +81,7 @@ pub use effectcraft_raster::{AuxChannels, Image};
|
|||
pub use misc::{INVERT_ALPHA, INVERT_CHANNELS, glow_operation};
|
||||
pub use sim::particle_state;
|
||||
pub use sim3::playground_state;
|
||||
pub use stylize2::strobe_on;
|
||||
// CPU helpers the GPU kernels share (effectcraft-gpu).
|
||||
pub use blur2::{camera_lens_plain, camera_lens_spans};
|
||||
pub use generate::gen_mode;
|
||||
|
|
@ -860,7 +862,37 @@ pub const GPU_EFFECTS: &[&str] = &[
|
|||
"ec.color.cccolorneutralizer",
|
||||
"ec.color.colorstabilizer",
|
||||
// effectcraft-gpu fx_stylize (stylize, distort)
|
||||
//
|
||||
"ec.stylize.posterize",
|
||||
"ec.stylize.threshold",
|
||||
"ec.stylize.ccthreshold",
|
||||
"ec.stylize.ccthresholdrgb",
|
||||
"ec.stylize.strobe",
|
||||
"ec.stylize.ccvignette",
|
||||
"ec.stylize.scatter",
|
||||
"ec.stylize.brushstrokes",
|
||||
"ec.stylize.roughenedges",
|
||||
"ec.stylize.texturize",
|
||||
"ec.stylize.motiontile",
|
||||
"ec.stylize.cckaleida",
|
||||
"ec.stylize.ccrepetile",
|
||||
"ec.distort.mirror",
|
||||
"ec.distort.offset",
|
||||
"ec.distort.polar",
|
||||
"ec.distort.spherize",
|
||||
"ec.distort.cornerpin",
|
||||
"ec.distort.opticscompensation",
|
||||
"ec.distort.magnify",
|
||||
"ec.distort.ccslant",
|
||||
"ec.distort.ccsmear",
|
||||
"ec.distort.ccsplit",
|
||||
"ec.distort.ccsplit2",
|
||||
"ec.distort.cctiler",
|
||||
"ec.distort.ccgriddler",
|
||||
"ec.distort.liquify",
|
||||
"ec.distort.twirllegacy",
|
||||
"ec.distort.ccripplepulse",
|
||||
"ec.distort.ccpowerpin",
|
||||
"ec.distort.ccflomotion",
|
||||
// effectcraft-gpu fx_noise (noise, blur, time)
|
||||
"ec.noise.turbulent",
|
||||
"ec.noise.median",
|
||||
|
|
|
|||
|
|
@ -223,7 +223,8 @@ fn scatter(ctx: &EffectCtx, mut b: Buf) -> Buf {
|
|||
|
||||
// ---- Strobe Light ----
|
||||
|
||||
pub(crate) fn strobe_on(time: f64, duration: f64, period: f64, prob: f64, seed: u32) -> bool {
|
||||
/// Whether Strobe Light flashes at `time` (also used by the GPU kernels).
|
||||
pub fn strobe_on(time: f64, duration: f64, period: f64, prob: f64, seed: u32) -> bool {
|
||||
if period <= 0.0 {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,6 +113,8 @@ fn apply(e: &mut Enc, id: &str, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
|||
"ec.blur.gaussian" => gaussian(e, ctx, b),
|
||||
"ec.blur.fastbox" => box_blur(e, ctx, b),
|
||||
"ec.blur.directional" => directional(e, ctx, b),
|
||||
"ec.stylize.glow" if crate::fx_stylize::glow_extra(ctx) => crate::fx_stylize::glow(e, ctx, b),
|
||||
"ec.distort.transform" if crate::fx_stylize::transform_blur(ctx) => crate::fx_stylize::transform(e, ctx, b),
|
||||
"ec.stylize.glow" => glow(e, ctx, b),
|
||||
"ec.perspective.dropshadow" => drop_shadow(e, ctx, b),
|
||||
"ec.distort.transform" => transform(e, ctx, b),
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,3 +1,925 @@
|
|||
// GPU effects (stylize and distort family): see src/fx_stylize.rs. Each entry point mirrors the CPU effect in
|
||||
// effectcraft-effects operation for operation. Every name here is prefixed `fxs_` (the
|
||||
// family files share one module).
|
||||
// family files share one module). Coordinates are pixel centres (x + 0.5, y + 0.5) in buffer
|
||||
// pixels, as in util::remap. Per-column / per-row tables in `data` hold coordinates the CPU
|
||||
// computes in f64 (anything with a floor or a wrap), so tile and wrap boundaries match exactly.
|
||||
|
||||
const FXS_TAU: f32 = 6.2831855;
|
||||
const FXS_PI: f32 = 3.1415927;
|
||||
const FXS_HALF_PI: f32 = 1.5707964;
|
||||
|
||||
fn fxs_inside(p: vec2<i32>) -> bool {
|
||||
let d = out_dims();
|
||||
return p.x < d.x && p.y < d.y;
|
||||
}
|
||||
|
||||
fn fxs_centre(p: vec2<i32>) -> vec2<f32> {
|
||||
return vec2<f32>(f32(p.x) + 0.5, f32(p.y) + 0.5);
|
||||
}
|
||||
|
||||
// util::unpremul: straight colour (zero below alpha 1e-6) and alpha (≥ 0).
|
||||
fn fxs_unpremul(px: vec4<f32>) -> vec4<f32> {
|
||||
let a = px.w;
|
||||
if (a > 1e-6) {
|
||||
return vec4<f32>(fxs_qdiv(px.xyz, a), a);
|
||||
}
|
||||
return vec4<f32>(0.0, 0.0, 0.0, max(a, 0.0));
|
||||
}
|
||||
|
||||
// util::smoothstep.
|
||||
fn fxs_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);
|
||||
}
|
||||
|
||||
// x / a rounded like the CPU's IEEE division (GPU division may be off by an ulp, which flips
|
||||
// thresholds on 8 / 16 bpc colours that sit exactly on them): one residual correction.
|
||||
fn fxs_qdiv(x: vec3<f32>, a: f32) -> vec3<f32> {
|
||||
let q = x / a;
|
||||
let r = fma(-q, vec3<f32>(a), x);
|
||||
return q + r / a;
|
||||
}
|
||||
|
||||
fn fxs_rem2(i: i32) -> i32 {
|
||||
return ((i % 2) + 2) % 2;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- per-pixel colour effects
|
||||
// u[0].x = mode:
|
||||
// 1 Posterize (misc::posterize): f[0].x = levels − 1
|
||||
// 2 Threshold (misc::threshold): f[0].x = level
|
||||
// 3 CC Threshold: u[0].yz = (channel, invert); f[0].xy = (threshold, blend)
|
||||
// 4 CC Threshold RGB: u[0].yzw = invert R, G, B; f[0] = (thresholds, blend)
|
||||
// 5 Strobe Light on colour: u[0].y = operator; f[0] = (strobe colour, blend)
|
||||
// 6 Strobe Light transparent (Image::scale_alpha): f[0].x = factor
|
||||
// 7 CC Vignette: f[0] = (cx, cy, distance, amount); f[1].x = pin highlights
|
||||
|
||||
fn fxs_bin(v: f32, t: f32, inv: bool) -> f32 {
|
||||
return select(0.0, 1.0, (v >= t) != inv);
|
||||
}
|
||||
|
||||
fn fxs_strobe_op(op: u32, v: f32, k: f32) -> f32 {
|
||||
switch op {
|
||||
case 1u: { return v + k; }
|
||||
case 2u: { return max(v - k, 0.0); }
|
||||
case 3u: { return v * k; }
|
||||
case 4u: { return abs(v - k); }
|
||||
case 5u: { return 1.0 - (1.0 - v) * (1.0 - k); }
|
||||
case 6u: { return max(v, k); }
|
||||
case 7u: { return min(v, k); }
|
||||
default: { return k; }
|
||||
}
|
||||
}
|
||||
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxs_point(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!fxs_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let px = textureLoad(src, p, 0);
|
||||
let mode = P.u[0].x;
|
||||
let a = px.w;
|
||||
if (mode == 6u) {
|
||||
textureStore(out, p, px * P.f[0].x);
|
||||
return;
|
||||
}
|
||||
if (a <= 0.0) {
|
||||
textureStore(out, p, px);
|
||||
return;
|
||||
}
|
||||
switch mode {
|
||||
case 1u: {
|
||||
let lv = P.f[0].x;
|
||||
let c = clamp(fxs_qdiv(px.xyz, a), vec3<f32>(0.0), vec3<f32>(1.0)) * lv;
|
||||
let o = vec3<f32>(round_away(c.x), round_away(c.y), round_away(c.z)) / lv;
|
||||
textureStore(out, p, vec4<f32>(o * a, a));
|
||||
}
|
||||
case 2u: {
|
||||
let v = select(0.0, 1.0, luminance(fxs_qdiv(px.xyz, a)) >= P.f[0].x);
|
||||
textureStore(out, p, vec4<f32>(vec3<f32>(v) * a, a));
|
||||
}
|
||||
case 3u: {
|
||||
let u = fxs_unpremul(px);
|
||||
let c = u.xyz;
|
||||
let t = P.f[0].x;
|
||||
let blend = P.f[0].y;
|
||||
let inv = P.u[0].z != 0u;
|
||||
var o = c;
|
||||
var na = u.w;
|
||||
switch P.u[0].y {
|
||||
case 1u: {
|
||||
o = vec3<f32>(fxs_bin(c.x, t, inv), fxs_bin(c.y, t, inv), fxs_bin(c.z, t, inv));
|
||||
}
|
||||
case 2u: {
|
||||
o = vec3<f32>(fxs_bin(rgb_to_hsl(c).y, t, inv));
|
||||
}
|
||||
case 3u: {
|
||||
na = fxs_bin(u.w, t, inv);
|
||||
}
|
||||
default: {
|
||||
o = vec3<f32>(fxs_bin(luminance(c), t, inv));
|
||||
}
|
||||
}
|
||||
o = o + (c - o) * blend;
|
||||
na = na + (u.w - na) * blend;
|
||||
textureStore(out, p, vec4<f32>(o * na, na));
|
||||
}
|
||||
case 4u: {
|
||||
let c = fxs_qdiv(px.xyz, a);
|
||||
let t = P.f[0].xyz;
|
||||
let o = vec3<f32>(fxs_bin(c.x, t.x, P.u[0].y != 0u), fxs_bin(c.y, t.y, P.u[0].z != 0u), fxs_bin(c.z, t.z, P.u[0].w != 0u));
|
||||
let r = o + (c - o) * P.f[0].w;
|
||||
textureStore(out, p, vec4<f32>(r * a, a));
|
||||
}
|
||||
case 5u: {
|
||||
let c = px.xyz / a;
|
||||
let k = P.f[0].xyz;
|
||||
let op = P.u[0].y;
|
||||
let o = vec3<f32>(fxs_strobe_op(op, c.x, k.x), fxs_strobe_op(op, c.y, k.y), fxs_strobe_op(op, c.z, k.z));
|
||||
let r = o + (c - o) * P.f[0].w;
|
||||
textureStore(out, p, vec4<f32>(r * a, a));
|
||||
}
|
||||
case 7u: {
|
||||
let u = fxs_unpremul(px);
|
||||
let q = fxs_centre(p);
|
||||
let dx = q.x - P.f[0].x;
|
||||
let dy = q.y - P.f[0].y;
|
||||
let r = sqrt(dx * dx + dy * dy) / P.f[0].z;
|
||||
let f0 = 1.0 / (1.0 + r * r);
|
||||
let f = f0 * f0;
|
||||
var k = 1.0 - P.f[0].w * (1.0 - f);
|
||||
let pin = P.f[1].x;
|
||||
if (pin > 0.0) {
|
||||
let l = clamp(luminance(u.xyz), 0.0, 1.0);
|
||||
k += (1.0 - k) * pin * l;
|
||||
}
|
||||
textureStore(out, p, vec4<f32>(max(u.xyz * k, vec3<f32>(0.0)) * u.w, u.w));
|
||||
}
|
||||
default: {
|
||||
textureStore(out, p, px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- inverse-mapped warps
|
||||
// u[0].x = mode (all sample bilinear, transparent outside, unless noted):
|
||||
// 1 Mirror (distort::mirror): f[0] = (cx, cy, nx, ny)
|
||||
// 2 Spherize (distort::spherize): f[0] = (cx, cy, radius)
|
||||
// 3 Polar Coordinates (distort::polar, edges repeated): u[0].y = to polar;
|
||||
// f[0] = (w, h, cx, cy); f[1] = (rmax, interpolation)
|
||||
// 4 CC Slant (distort3::cc_slant): f[0] = (floor x, floor y, 1 / height, slant); f[1].x = x scale
|
||||
// 5 CC Smear (distort3::cc_smear): f[0] = (from x, from y, dx, dy); f[1] = (len², reach, radius)
|
||||
// 6 CC Split (distort3::split_impl): f[0] = (ax, ay, ux, uy); f[1] = (len, split 1, split 2, reach)
|
||||
// 7 Optics Compensation (distort2::optics_compensation): u[0].y = reverse;
|
||||
// f[0] = (cx, cy, fit, f); f[1] = (reference radius, half FOV)
|
||||
// 8 CC Kaleida (stylize2::kaleida, edges repeated): u[0].y = mirror;
|
||||
// f[0] = (cx, cy, size, rotation); f[1].x = segment angle
|
||||
// 9 Liquify (distort4::liquify): u[0].xy = (_, mesh nx, mesh ny); f[0] = (cell, mesh offset x, y,
|
||||
// percentage); f[1] = (scale, buffer offset x, y); data = mesh offsets (x, y) per node
|
||||
// 10 Twirl (Legacy) (distort3::twirl_legacy): f[0] = (cx, cy, radius, angle)
|
||||
// 11 CC Ripple Pulse (distort3::cc_ripple_pulse): u[0].y = render bump; f[0] = (cx, cy, front,
|
||||
// speed); f[1] = (time span, wavelength, height)
|
||||
// 12 CC Power Pin, full perspective (distort3::cc_power_pin): f[2..5] = inverse of the
|
||||
// square → quad matrix (rows); f[5] = source rect in layer pixels (x0, y0, w, h);
|
||||
// f[6] = (scale, buffer offset x, y)
|
||||
|
||||
fn fxs_mesh_d(i: u32, j: u32) -> vec2<f32> {
|
||||
let k = (j * P.u[0].y + i) * 2u;
|
||||
return vec2<f32>(data[k], data[k + 1u]);
|
||||
}
|
||||
|
||||
// distort3::cc_ripple_pulse's wave.
|
||||
fn fxs_pulse(r: f32) -> f32 {
|
||||
let front = P.f[0].z;
|
||||
let speed = P.f[0].w;
|
||||
let span = P.f[1].x;
|
||||
let age = (front - r) / max(speed, 1e-9);
|
||||
if (age < 0.0 || age > span) {
|
||||
return 0.0;
|
||||
}
|
||||
let fade = 1.0 - age / span;
|
||||
return sin(FXS_TAU * (r - front) / P.f[1].y) * fade;
|
||||
}
|
||||
|
||||
// distort4::Mesh::at.
|
||||
fn fxs_mesh_at(x: f32, y: f32) -> vec2<f32> {
|
||||
let cell = P.f[0].x;
|
||||
let nx = P.u[0].y;
|
||||
let ny = P.u[0].z;
|
||||
let fx = x / cell;
|
||||
let fy = y / cell;
|
||||
if (fx < 0.0 || fy < 0.0 || fx > f32(nx - 1u) || fy > f32(ny - 1u)) {
|
||||
return vec2<f32>(0.0);
|
||||
}
|
||||
let x0 = min(u32(floor(fx)), nx - 2u);
|
||||
let y0 = min(u32(floor(fy)), ny - 2u);
|
||||
let x1 = min(x0 + 1u, nx - 1u);
|
||||
let y1 = min(y0 + 1u, ny - 1u);
|
||||
let tx = fx - f32(x0);
|
||||
let ty = fy - f32(y0);
|
||||
let a = fxs_mesh_d(x0, y0);
|
||||
let b = fxs_mesh_d(x1, y0);
|
||||
let c = fxs_mesh_d(x0, y1);
|
||||
let d = fxs_mesh_d(x1, y1);
|
||||
let top = a + (b - a) * tx;
|
||||
let bot = c + (d - c) * tx;
|
||||
return top + (bot - top) * ty;
|
||||
}
|
||||
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxs_warp(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!fxs_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let q = fxs_centre(p);
|
||||
let x = q.x;
|
||||
let y = q.y;
|
||||
var s = q;
|
||||
var clamped = false;
|
||||
switch P.u[0].x {
|
||||
case 1u: {
|
||||
let n = P.f[0].zw;
|
||||
let d = (x - P.f[0].x) * n.x + (y - P.f[0].y) * n.y;
|
||||
if (d > 0.0) {
|
||||
s = vec2<f32>(x - 2.0 * d * n.x, y - 2.0 * d * n.y);
|
||||
}
|
||||
}
|
||||
case 2u: {
|
||||
let c = P.f[0].xy;
|
||||
let r = P.f[0].z;
|
||||
let dx = x - c.x;
|
||||
let dy = y - c.y;
|
||||
let d = sqrt(dx * dx + dy * dy);
|
||||
if (d < r && d != 0.0) {
|
||||
let nd = d / r;
|
||||
let k = (asin(nd) / FXS_HALF_PI) / nd;
|
||||
s = vec2<f32>(c.x + dx * k, c.y + dy * k);
|
||||
}
|
||||
}
|
||||
case 3u: {
|
||||
clamped = true;
|
||||
let w = P.f[0].x;
|
||||
let h = P.f[0].y;
|
||||
let cx = P.f[0].z;
|
||||
let cy = P.f[0].w;
|
||||
let rmax = P.f[1].x;
|
||||
var pp: vec2<f32>;
|
||||
if (P.u[0].y != 0u) {
|
||||
let dx = x - cx;
|
||||
let dy = y - cy;
|
||||
let a0 = atan2(dx, -dy);
|
||||
let a = a0 - floor(a0 / FXS_TAU) * FXS_TAU;
|
||||
let r = sqrt(dx * dx + dy * dy) / rmax;
|
||||
pp = vec2<f32>(a / FXS_TAU * w, h - r * h);
|
||||
} else {
|
||||
let a = x / w * FXS_TAU;
|
||||
let r = (h - y) / h * rmax;
|
||||
pp = vec2<f32>(cx + r * sin(a), cy - r * cos(a));
|
||||
}
|
||||
let amt = P.f[1].y;
|
||||
s = vec2<f32>(x + (pp.x - x) * amt, y + (pp.y - y) * amt);
|
||||
}
|
||||
case 4u: {
|
||||
let fl = P.f[0].xy;
|
||||
s = vec2<f32>(fl.x + (x - fl.x - (fl.y - y) * P.f[0].w) / P.f[1].x, fl.y + (y - fl.y) * P.f[0].z);
|
||||
}
|
||||
case 5u: {
|
||||
let fr = P.f[0].xy;
|
||||
let dx = P.f[0].z;
|
||||
let dy = P.f[0].w;
|
||||
let t = clamp(((x - fr.x) * dx + (y - fr.y) * dy) / P.f[1].x, 0.0, 1.0);
|
||||
let px = fr.x + dx * t;
|
||||
let py = fr.y + dy * t;
|
||||
let d = sqrt((x - px) * (x - px) + (y - py) * (y - py));
|
||||
let w0 = max(1.0 - d / P.f[1].z, 0.0);
|
||||
let w = w0 * w0 * (3.0 - 2.0 * w0);
|
||||
let k = P.f[1].y * w * t;
|
||||
s = vec2<f32>(x - dx * k, y - dy * k);
|
||||
}
|
||||
case 6u: {
|
||||
let a = P.f[0].xy;
|
||||
let ux = P.f[0].z;
|
||||
let uy = P.f[0].w;
|
||||
let nx = -uy;
|
||||
let ny = ux;
|
||||
let len = P.f[1].x;
|
||||
let reach = P.f[1].w;
|
||||
let rx = x - a.x;
|
||||
let ry = y - a.y;
|
||||
let t = (rx * ux + ry * uy) / len;
|
||||
let sd = rx * nx + ry * ny;
|
||||
if (t >= 0.0 && t <= 1.0) {
|
||||
let amt = select(P.f[1].z, P.f[1].y, sd >= 0.0);
|
||||
let g = amt * reach * sin(FXS_PI * t);
|
||||
let ad = abs(sd);
|
||||
if (ad < g) {
|
||||
textureStore(out, p, vec4<f32>(0.0));
|
||||
return;
|
||||
}
|
||||
let push = g * max(1.0 - (ad - g) / reach, 0.0);
|
||||
let ns = select(-1.0, 1.0, sd >= 0.0) * (ad - push);
|
||||
s = vec2<f32>(x + nx * (ns - sd), y + ny * (ns - sd));
|
||||
}
|
||||
}
|
||||
case 7u: {
|
||||
let c = P.f[0].xy;
|
||||
let fit = P.f[0].z;
|
||||
let f = P.f[0].w;
|
||||
let r_ref = P.f[1].x;
|
||||
let half = P.f[1].y;
|
||||
let dx = x - c.x;
|
||||
let dy = y - c.y;
|
||||
let r = sqrt(dx * dx + dy * dy) * fit;
|
||||
if (r >= 1e-9) {
|
||||
var rs: f32;
|
||||
if (P.u[0].y != 0u) {
|
||||
rs = atan(r / f) / half * r_ref;
|
||||
} else {
|
||||
let th = r / r_ref * half;
|
||||
if (th >= FXS_HALF_PI - 1e-4) {
|
||||
textureStore(out, p, vec4<f32>(0.0));
|
||||
return;
|
||||
}
|
||||
rs = f * tan(th);
|
||||
}
|
||||
let k = rs / r * fit;
|
||||
s = vec2<f32>(c.x + dx * k, c.y + dy * k);
|
||||
}
|
||||
}
|
||||
case 8u: {
|
||||
clamped = true;
|
||||
let c = P.f[0].xy;
|
||||
let rot = P.f[0].w;
|
||||
let seg = P.f[1].x;
|
||||
let dx = x - c.x;
|
||||
let dy = y - c.y;
|
||||
let r = sqrt(dx * dx + dy * dy) / P.f[0].z;
|
||||
let a0 = atan2(dy, dx) - rot;
|
||||
var a = a0 - floor(a0 / seg) * seg;
|
||||
if (P.u[0].y != 0u && a > seg * 0.5) {
|
||||
a = seg - a;
|
||||
}
|
||||
s = vec2<f32>(c.x + r * cos(a + rot), c.y + r * sin(a + rot));
|
||||
}
|
||||
case 9u: {
|
||||
let scale = P.f[1].x;
|
||||
let o = P.f[1].yz;
|
||||
let lx = (x - o.x) / scale;
|
||||
let ly = (y - o.y) / scale;
|
||||
let d = fxs_mesh_at(lx - P.f[0].y, ly - P.f[0].z);
|
||||
if (d.x == 0.0 && d.y == 0.0) {
|
||||
textureStore(out, p, textureLoad(src, p, 0));
|
||||
return;
|
||||
}
|
||||
let pct = P.f[0].w;
|
||||
s = vec2<f32>((lx + d.x * pct) * scale + o.x, (ly + d.y * pct) * scale + o.y);
|
||||
}
|
||||
case 10u: {
|
||||
let c = P.f[0].xy;
|
||||
let r = P.f[0].z;
|
||||
let dx = x - c.x;
|
||||
let dy = y - c.y;
|
||||
let d = sqrt(dx * dx + dy * dy);
|
||||
if (d < r) {
|
||||
let a = -P.f[0].w * (1.0 - d / r);
|
||||
let sn = sin(a);
|
||||
let co = cos(a);
|
||||
s = vec2<f32>(c.x + dx * co - dy * sn, c.y + dx * sn + dy * co);
|
||||
}
|
||||
}
|
||||
case 11u: {
|
||||
let c = P.f[0].xy;
|
||||
let dx = x - c.x;
|
||||
let dy = y - c.y;
|
||||
let r = sqrt(dx * dx + dy * dy);
|
||||
let height = P.f[1].z;
|
||||
let d = fxs_pulse(r) * height;
|
||||
var u = vec2<f32>(0.0);
|
||||
if (r > 1e-9) {
|
||||
u = vec2<f32>(dx / r, dy / r);
|
||||
}
|
||||
var o = sample_bilinear(src, x - u.x * d, y - u.y * d);
|
||||
if (P.u[0].y != 0u && d != 0.0) {
|
||||
let slope = (fxs_pulse(r + 0.5) - fxs_pulse(r - 0.5)) * height;
|
||||
let k = clamp(1.0 - slope * 0.5, 0.0, 2.0);
|
||||
o = vec4<f32>(o.xyz * k, o.w);
|
||||
}
|
||||
textureStore(out, p, o);
|
||||
return;
|
||||
}
|
||||
case 12u: {
|
||||
let r0 = P.f[2];
|
||||
let r1 = P.f[3];
|
||||
let r2 = P.f[4];
|
||||
var u = r0.x * x + r0.y * y + r0.z;
|
||||
var v = r1.x * x + r1.y * y + r1.z;
|
||||
let w = r2.x * x + r2.y * y + r2.z;
|
||||
if (w != 1.0 && w != 0.0) {
|
||||
u = u / w;
|
||||
v = v / w;
|
||||
}
|
||||
if (u < -1e-9 || u > 1.0 + 1e-9 || v < -1e-9 || v > 1.0 + 1e-9) {
|
||||
textureStore(out, p, vec4<f32>(0.0));
|
||||
return;
|
||||
}
|
||||
let rc = P.f[5];
|
||||
let lx = rc.x + u * rc.z;
|
||||
let ly = rc.y + v * rc.w;
|
||||
let o = P.f[6];
|
||||
s = vec2<f32>(lx * o.x + o.y, ly * o.x + o.z);
|
||||
}
|
||||
default: {}
|
||||
}
|
||||
if (clamped) {
|
||||
textureStore(out, p, sample_bilinear_clamped(src, s.x, s.y));
|
||||
} else {
|
||||
textureStore(out, p, sample_bilinear(src, s.x, s.y));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- CC Flo Motion (distort4::flo_motion)
|
||||
|
||||
// One knot's pull at layer point q: f[k] = (knot x, knot y, amount, _).
|
||||
fn fxs_flo_knot(q: vec2<f32>, k: vec4<f32>) -> vec2<f32> {
|
||||
if (k.z == 0.0) {
|
||||
return q;
|
||||
}
|
||||
let d = q - k.xy;
|
||||
let r = sqrt(d.x * d.x + d.y * d.y) / P.f[2].y;
|
||||
let w = powz(1.0 - min(r, 1.0), P.f[2].x);
|
||||
let s = max(1.0 - clamp(k.z, -4.0, 0.95) * w, 0.02);
|
||||
return k.xy + d * s;
|
||||
}
|
||||
|
||||
// u[0] = (supersampling, tile edges); f[0], f[1] = knots; f[2] = (falloff, radius, layer w,
|
||||
// layer h); f[3] = (scale, buffer offset x, y)
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxs_flomotion(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!fxs_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let ss = P.u[0].x;
|
||||
let scale = P.f[3].x;
|
||||
let off = P.f[3].yz;
|
||||
let lw = P.f[2].z;
|
||||
let lh = P.f[2].w;
|
||||
var acc = vec4<f32>(0.0);
|
||||
for (var sy = 0u; sy < ss; sy++) {
|
||||
for (var sx = 0u; sx < ss; sx++) {
|
||||
let px = f32(p.x) + (f32(sx) + 0.5) / f32(ss);
|
||||
let py = f32(p.y) + (f32(sy) + 0.5) / f32(ss);
|
||||
var q = vec2<f32>((px - off.x) / scale, (py - off.y) / scale);
|
||||
q = fxs_flo_knot(q, P.f[0]);
|
||||
q = fxs_flo_knot(q, P.f[1]);
|
||||
if (P.u[0].y != 0u) {
|
||||
q = vec2<f32>(q.x - floor(q.x / lw) * lw, q.y - floor(q.y / lh) * lh);
|
||||
}
|
||||
acc += sample_bilinear(src, q.x * scale + off.x, q.y * scale + off.y);
|
||||
}
|
||||
}
|
||||
textureStore(out, p, acc / f32(ss * ss));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- separable table warps
|
||||
// The source point is (data[x], data[w + y]) (computed in f64 per column and per row), then
|
||||
// mixed with the original: out = sample + (orig − sample) · blend (Offset, CC Tiler).
|
||||
// u[0] = (buffer width, repeat edges); f[0].x = blend
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxs_table(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!fxs_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let w = P.u[0].x;
|
||||
let sx = data[u32(p.x)];
|
||||
let sy = data[w + u32(p.y)];
|
||||
var s: vec4<f32>;
|
||||
if (P.u[0].y != 0u) {
|
||||
s = sample_bilinear_clamped(src, sx, sy);
|
||||
} else {
|
||||
s = sample_bilinear(src, sx, sy);
|
||||
}
|
||||
let o = textureLoad(src, p, 0);
|
||||
textureStore(out, p, s + (o - s) * P.f[0].x);
|
||||
}
|
||||
|
||||
// CC Griddler (distort2::cc_griddler). data: per column (tile centre x, local x), per row
|
||||
// (tile centre y, local y). u[0] = (buffer width, cut tiles); f[0] = (cos, sin, h scale, v scale);
|
||||
// f[1].x = cut half size (0.45 · tile)
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxs_griddler(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!fxs_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let w = P.u[0].x;
|
||||
let x = u32(p.x);
|
||||
let y = u32(p.y);
|
||||
let tcx = data[2u * x];
|
||||
let lx = data[2u * x + 1u];
|
||||
let tcy = data[2u * w + 2u * y];
|
||||
let ly = data[2u * w + 2u * y + 1u];
|
||||
if (P.u[0].y != 0u && max(abs(lx), abs(ly)) > P.f[1].x) {
|
||||
textureStore(out, p, vec4<f32>(0.0));
|
||||
return;
|
||||
}
|
||||
let cr = P.f[0].x;
|
||||
let sr = P.f[0].y;
|
||||
let rx = lx * cr + ly * sr;
|
||||
let ry = -lx * sr + ly * cr;
|
||||
textureStore(out, p, sample_bilinear(src, tcx + rx / P.f[0].z, tcy + ry / P.f[0].w));
|
||||
}
|
||||
|
||||
// Motion Tile (stylize2::motion_tile) into a new output size. data: per output column (tile
|
||||
// index, fraction) of tx, then per row of ty. u[0] = (output width, horizontal phase shift,
|
||||
// mirror edges); f[0] = (phase, layer w · scale, layer h · scale); f[1].xy = source buffer offset
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxs_motiontile(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!fxs_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let w = P.u[0].x;
|
||||
let x = u32(p.x);
|
||||
let y = u32(p.y);
|
||||
let ixc = data[2u * x];
|
||||
let fxc = data[2u * x + 1u];
|
||||
let iyr = data[2u * w + 2u * y];
|
||||
let fyr = data[2u * w + 2u * y + 1u];
|
||||
let phase = P.f[0].x;
|
||||
var i = ixc;
|
||||
var fx = fxc;
|
||||
var j = iyr;
|
||||
var fy = fyr;
|
||||
if (P.u[0].y != 0u) {
|
||||
let t = fxc + phase * iyr;
|
||||
i = ixc + floor(t);
|
||||
fx = t - floor(t);
|
||||
} else {
|
||||
let t = fyr + phase * ixc;
|
||||
j = iyr + floor(t);
|
||||
fy = t - floor(t);
|
||||
}
|
||||
if (P.u[0].z != 0u) {
|
||||
if (fxs_rem2(i32(i)) == 1) {
|
||||
fx = 1.0 - fx;
|
||||
}
|
||||
if (fxs_rem2(i32(j)) == 1) {
|
||||
fy = 1.0 - fy;
|
||||
}
|
||||
}
|
||||
textureStore(out, p, sample_bilinear(src, fx * P.f[0].y + P.f[1].x, fy * P.f[0].z + P.f[1].y));
|
||||
}
|
||||
|
||||
// CC RepeTile (stylize2::repetile) into a new output size. data: per output column (tile index,
|
||||
// position in the tile), then per row. u[0] = (output width, tiling); f[0] = layer rect (x0, y0, w, h)
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxs_repetile(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!fxs_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let w = P.u[0].x;
|
||||
let x = u32(p.x);
|
||||
let y = u32(p.y);
|
||||
let ix = i32(data[2u * x]);
|
||||
var fx = data[2u * x + 1u];
|
||||
let iy = i32(data[2u * w + 2u * y]);
|
||||
var fy = data[2u * w + 2u * y + 1u];
|
||||
let odd = fxs_rem2(ix + iy) == 1;
|
||||
var flip_x = false;
|
||||
var flip_y = false;
|
||||
switch P.u[0].y {
|
||||
case 1u: {
|
||||
flip_x = fxs_rem2(ix) == 1;
|
||||
flip_y = fxs_rem2(iy) == 1;
|
||||
}
|
||||
case 2u: {
|
||||
flip_x = odd;
|
||||
}
|
||||
case 3u: {
|
||||
flip_y = odd;
|
||||
}
|
||||
case 4u: {
|
||||
flip_x = odd;
|
||||
flip_y = odd;
|
||||
}
|
||||
default: {}
|
||||
}
|
||||
let r = P.f[0];
|
||||
if (flip_x) {
|
||||
fx = r.z - fx;
|
||||
}
|
||||
if (flip_y) {
|
||||
fy = r.w - fy;
|
||||
}
|
||||
textureStore(out, p, sample_bilinear(src, r.x + fx, r.y + fy));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Magnify (distort2::magnify)
|
||||
// data: per column (dx, sx, floor(sx) + 0.5), then per row (dy, sy, floor(sy) + 0.5).
|
||||
// u[0] = (buffer width, square, scaling, blend: 0 none, 1 normal, 2 blend mode);
|
||||
// u[1] = (blend mode id, seed); f[0] = (size, feather, opacity, magnification); f[1].x = size − feather
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxs_magnify(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!fxs_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let bw = P.u[0].x;
|
||||
let x = u32(p.x);
|
||||
let y = u32(p.y);
|
||||
let dx = data[3u * x];
|
||||
let sx = data[3u * x + 1u];
|
||||
let nx = data[3u * x + 2u];
|
||||
let dy = data[3u * bw + 3u * y];
|
||||
let sy = data[3u * bw + 3u * y + 1u];
|
||||
let ny = data[3u * bw + 3u * y + 2u];
|
||||
var d: f32;
|
||||
if (P.u[0].y != 0u) {
|
||||
d = max(abs(dx), abs(dy));
|
||||
} else {
|
||||
d = sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
let size = P.f[0].x;
|
||||
let feather = P.f[0].y;
|
||||
var w: f32;
|
||||
if (feather > 0.01) {
|
||||
w = 1.0 - fxs_smoothstep(P.f[1].x, size, d);
|
||||
} else {
|
||||
w = clamp(size - d + 0.5, 0.0, 1.0);
|
||||
}
|
||||
w = w * P.f[0].z;
|
||||
let px = textureLoad(src, p, 0);
|
||||
let blend = P.u[0].w;
|
||||
if (w <= 0.0) {
|
||||
if (blend == 0u) {
|
||||
textureStore(out, p, vec4<f32>(0.0));
|
||||
} else {
|
||||
textureStore(out, p, px);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var m: vec4<f32>;
|
||||
switch P.u[0].z {
|
||||
case 1u: {
|
||||
m = sample_bicubic(src, sx, sy);
|
||||
}
|
||||
case 2u: {
|
||||
let seed = P.u[1].y;
|
||||
let k = max(1.0 - 1.0 / P.f[0].w, 0.0);
|
||||
let j1 = (hash_noise(x, y ^ (1u << 20u), seed) - 0.5) * k;
|
||||
let j2 = (hash_noise(x, y ^ (2u << 20u), seed) - 0.5) * k;
|
||||
m = sample_bilinear(src, nx + j1, ny + j2);
|
||||
}
|
||||
default: {
|
||||
m = sample_bilinear(src, nx, ny);
|
||||
}
|
||||
}
|
||||
if (blend == 0u) {
|
||||
textureStore(out, p, m * w);
|
||||
} else if (blend == 1u) {
|
||||
textureStore(out, p, px + (m - px) * w);
|
||||
} else {
|
||||
textureStore(out, p, blend_pixel(P.u[1].x, px, m * w, 0.5));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Scatter (stylize2::scatter)
|
||||
// u[0] = (grain, seed); f[0].x = amount
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxs_scatter(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!fxs_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let grain = P.u[0].x;
|
||||
let seed = P.u[0].y;
|
||||
let amt = P.f[0].x;
|
||||
let x = u32(p.x);
|
||||
let y = u32(p.y);
|
||||
var dx = 0.0;
|
||||
var dy = 0.0;
|
||||
if (grain != 2u) {
|
||||
dx = (hash_noise(x, y, seed) - 0.5) * 2.0 * amt;
|
||||
}
|
||||
if (grain != 1u) {
|
||||
dy = (hash_noise(x, y, seed ^ 0x5bd1u) - 0.5) * 2.0 * amt;
|
||||
}
|
||||
textureStore(out, p, tex_get(src, i32(round_away(f32(p.x) + dx)), i32(round_away(f32(p.y) + dy))));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Brush Strokes (stylize2::brush_strokes)
|
||||
// data: cell column per x (w), cell row per y (h), then per cell (dx, dy, length, jitter, samples,
|
||||
// painted). u[0] = (buffer width, cell columns, paint surface, buffer height); f[0].x = blend
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxs_brush(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!fxs_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let w = P.u[0].x;
|
||||
let h = P.u[0].w;
|
||||
let cx = u32(data[u32(p.x)]);
|
||||
let cy = u32(data[w + u32(p.y)]);
|
||||
let base = w + h + (cy * P.u[0].y + cx) * 6u;
|
||||
let dx = data[base];
|
||||
let dy = data[base + 1u];
|
||||
let l = data[base + 2u];
|
||||
let jit = data[base + 3u];
|
||||
let n = u32(data[base + 4u]);
|
||||
let m = data[base + 5u];
|
||||
let q = fxs_centre(p);
|
||||
let bx = q.x - dy * jit;
|
||||
let by = q.y + dx * jit;
|
||||
var acc = vec4<f32>(0.0);
|
||||
for (var i = 0u; i < n; i++) {
|
||||
var t = 0.0;
|
||||
if (n != 1u) {
|
||||
t = (f32(i) / f32(n - 1u) - 0.5) * l;
|
||||
}
|
||||
acc += sample_bilinear_clamped(src, bx + dx * t, by + dy * t);
|
||||
}
|
||||
let stroke = acc / f32(n);
|
||||
let orig = textureLoad(src, p, 0);
|
||||
let surface = P.u[0].z;
|
||||
var base_px = orig;
|
||||
if (surface == 1u) {
|
||||
base_px = vec4<f32>(0.0);
|
||||
} else if (surface == 2u) {
|
||||
base_px = vec4<f32>(1.0);
|
||||
} else if (surface == 3u) {
|
||||
base_px = vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
var painted: vec4<f32>;
|
||||
if (surface == 2u || surface == 3u) {
|
||||
let k = 1.0 - stroke.w * m;
|
||||
painted = stroke * m + base_px * k;
|
||||
} else {
|
||||
painted = base_px + (stroke - base_px) * m;
|
||||
}
|
||||
textureStore(out, p, painted + (orig - painted) * P.f[0].x);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Roughen Edges (stylize2::roughen_edges)
|
||||
|
||||
// noise::fbm.
|
||||
fn fxs_fbm(u: f32, v: f32, z: f32, seed: u32, octaves0: f32) -> f32 {
|
||||
let octaves = clamp(octaves0, 1.0, 20.0);
|
||||
let n = u32(ceil(octaves));
|
||||
let frac = octaves - floor(octaves);
|
||||
var sum = 0.0;
|
||||
var norm = 0.0;
|
||||
var amp = 1.0;
|
||||
var f = 1.0;
|
||||
for (var o = 0u; o < n; o++) {
|
||||
var w = 1.0;
|
||||
if (o + 1u == n && frac > 0.0) {
|
||||
w = frac;
|
||||
}
|
||||
sum += value_noise(u * f, v * f, z + f32(o) * 7.31, seed + o) * amp * w;
|
||||
norm += amp * w;
|
||||
amp *= 0.5;
|
||||
f *= 2.0;
|
||||
}
|
||||
return sum / max(norm, 1e-6);
|
||||
}
|
||||
|
||||
// aux = the (blurred) alpha in .w. u[0] = (seed, cycle evolution, coloured);
|
||||
// f[0] = (offset x, offset y, scale · stretch x, scale · stretch y); f[1] = (frequency, evolution,
|
||||
// cycle, cycle weight); f[2] = (octaves, influence, influence multiplier, sharpness);
|
||||
// f[3] = (edge colour, sharpness multiplier)
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxs_roughen(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!fxs_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let px = textureLoad(src, p, 0);
|
||||
if (px.w <= 0.0) {
|
||||
textureStore(out, p, px);
|
||||
return;
|
||||
}
|
||||
let dv = textureLoad(aux, p, 0).w;
|
||||
let fm = P.f[1].x;
|
||||
let nx = (f32(p.x) + 0.5 - P.f[0].x) / P.f[0].z * fm;
|
||||
let ny = (f32(p.y) + 0.5 - P.f[0].y) / P.f[0].w * fm;
|
||||
let seed = P.u[0].x;
|
||||
let evo = P.f[1].y;
|
||||
let oct = P.f[2].x;
|
||||
var n = fxs_fbm(nx, ny, evo, seed, oct);
|
||||
if (P.u[0].y != 0u) {
|
||||
n += (fxs_fbm(nx, ny, evo - P.f[1].z, seed, oct) - n) * P.f[1].w;
|
||||
}
|
||||
let infl = P.f[2].y;
|
||||
let s = dv - 0.5 + (n - 0.5) * infl * P.f[2].z * 0.5;
|
||||
let m = clamp(s * P.f[2].w * P.f[3].w * 4.0 + 0.5, 0.0, 1.0);
|
||||
let u = fxs_unpremul(px);
|
||||
var c = u.xyz;
|
||||
if (P.u[0].z != 0u) {
|
||||
let band = 1.0 - fxs_smoothstep(0.5, 0.9, dv + (n - 0.5) * infl * 0.5);
|
||||
c = c + (P.f[3].xyz - c) * band;
|
||||
}
|
||||
let a = u.w * m;
|
||||
textureStore(out, p, vec4<f32>(c * a, a));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Texturize (stylize2::texturize)
|
||||
|
||||
// The texture layer placed into the buffer (transition::place_layer) as luminance
|
||||
// (Plane::luma). aux = the texture layer's buffer; data: per column x, then per row y in its
|
||||
// pixels. u[0].x = buffer width
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxs_tex_luma(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!fxs_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let w = P.u[0].x;
|
||||
let t = sample_bilinear(aux, data[u32(p.x)], data[w + u32(p.y)]);
|
||||
let l = luminance(fxs_unpremul(t).xyz);
|
||||
textureStore(out, p, vec4<f32>(l));
|
||||
}
|
||||
|
||||
// Relief shading. aux = the blurred texture luminance; f[0] = (light x, light y, contrast)
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxs_texturize(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!fxs_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let gx = (tex_get_clamped(aux, p.x + 1, p.y).x - tex_get_clamped(aux, p.x - 1, p.y).x) * 0.5;
|
||||
let gy = (tex_get_clamped(aux, p.x, p.y + 1).x - tex_get_clamped(aux, p.x, p.y - 1).x) * 0.5;
|
||||
let shade = max(1.0 - P.f[0].z * 4.0 * (gx * P.f[0].x + gy * P.f[0].y), 0.0);
|
||||
let px = textureLoad(src, p, 0);
|
||||
textureStore(out, p, vec4<f32>(px.xyz * shade, px.w));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Glow (misc::glow) extras
|
||||
|
||||
// Bright pass with the Arbitrary Map: as `glow_bright` (same f[0] / u[0] layout, see
|
||||
// kernels.wgsl) with the red, green and blue curves at data offsets f[1].xyz (−1 = identity).
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxs_glow_map(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!fxs_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let px = textureLoad(src, p, 0);
|
||||
let a = px.w;
|
||||
if (a <= 0.0) {
|
||||
textureStore(out, p, vec4<f32>(0.0));
|
||||
return;
|
||||
}
|
||||
let thr = P.f[0].x;
|
||||
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);
|
||||
let t = glow_ab_t(l);
|
||||
let c = vec3<f32>(curve_eval(P.f[1].x, t), curve_eval(P.f[1].y, t), curve_eval(P.f[1].z, t));
|
||||
textureStore(out, p, vec4<f32>(c * k * a, k * a));
|
||||
}
|
||||
|
||||
// On Top with another Glow Operation: blend_pixel(mode, original, glow · intensity, 0).
|
||||
// aux = the blurred glow; u[0].x = blend mode id; f[0].x = intensity
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxs_glow_op(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!fxs_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let g = textureLoad(aux, p, 0) * P.f[0].x;
|
||||
var o = blend_pixel(P.u[0].x, textureLoad(src, p, 0), g, 0.0);
|
||||
o.w = clamp(o.w, 0.0, 1.0);
|
||||
textureStore(out, p, o);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- accumulation (Transform motion blur)
|
||||
|
||||
// out = src + aux
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxs_add(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!fxs_inside(p)) {
|
||||
return;
|
||||
}
|
||||
textureStore(out, p, textureLoad(src, p, 0) + textureLoad(aux, p, 0));
|
||||
}
|
||||
|
||||
// out = src / f[0].x
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxs_div(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!fxs_inside(p)) {
|
||||
return;
|
||||
}
|
||||
textureStore(out, p, textureLoad(src, p, 0) / P.f[0].x);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -511,7 +511,7 @@ fn effect_chains_and_mixed_stacks() {
|
|||
let mut l = s.footage(80, 50);
|
||||
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.coloremboss", &[]);
|
||||
s.effect(&mut l, "ec.stylize.glow", &[]);
|
||||
s.effect(&mut l, "ec.channel.invert", &[("blend", n(50.0))]);
|
||||
s.push(l);
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ fn gpu_adjustment_chains_match_the_cpu() {
|
|||
#[test]
|
||||
fn mixed_adjustment_chains_fall_back_per_effect() {
|
||||
let mut stack = gpu_stack();
|
||||
stack.insert(1, ("ec.stylize.posterize", vec![]));
|
||||
stack.insert(1, ("ec.stylize.coloremboss", vec![]));
|
||||
stack.push(("ec.stylize.glow", vec![("threshold", n(30.0))]));
|
||||
for depth in [BitDepth::Bpc8, BitDepth::Bpc32] {
|
||||
let s = adjustment_scene(depth, &stack, true);
|
||||
|
|
@ -93,7 +93,7 @@ impl FxTarget for Probe {
|
|||
fn adjustment_stacks_split_into_gpu_runs_and_cpu_steps() {
|
||||
let Some(g) = gpu() else { return };
|
||||
let mut stack = gpu_stack();
|
||||
stack.insert(1, ("ec.stylize.posterize", vec![]));
|
||||
stack.insert(1, ("ec.stylize.coloremboss", vec![]));
|
||||
let s = adjustment_scene(BitDepth::Bpc32, &stack, false);
|
||||
let mut r = Renderer::new(&s.p, &Pattern, RenderOpts { backend: Backend::Gpu, ..opts() });
|
||||
r.accel = Some(g);
|
||||
|
|
|
|||
|
|
@ -1,2 +1,223 @@
|
|||
//! GPU stylize and distort 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::{Keyframe, Value};
|
||||
use effectcraft_project::BitDepth;
|
||||
use effectcraft_render::RenderOpts;
|
||||
use effectcraft_time::Tick;
|
||||
|
||||
use crate::tests::{Scene, c, check, compare_at, effect_case, n, opts, set};
|
||||
|
||||
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 posterize_thresholds_strobe_vignette() {
|
||||
effect_case("ec.stylize.posterize", &[("level", n(4.0))]);
|
||||
effect_case("ec.stylize.posterize", &[("level", n(9.0))]);
|
||||
effect_case("ec.stylize.threshold", &[("level", n(100.0))]);
|
||||
effect_case("ec.stylize.threshold", &[("level", n(181.0))]);
|
||||
effect_case("ec.stylize.ccthreshold", &[]);
|
||||
effect_case("ec.stylize.ccthreshold", &[("channel", e(1)), ("invert", on()), ("threshold", n(90.0))]);
|
||||
effect_case("ec.stylize.ccthreshold", &[("channel", e(2)), ("blend", n(30.0))]);
|
||||
effect_case("ec.stylize.ccthreshold", &[("channel", e(3)), ("threshold", n(60.0))]);
|
||||
effect_case(
|
||||
"ec.stylize.ccthresholdrgb",
|
||||
&[("redThreshold", n(70.0)), ("greenThreshold", n(150.0)), ("blueThreshold", n(110.0)), ("invertGreen", on()), ("blend", n(25.0))],
|
||||
);
|
||||
effect_case(
|
||||
"ec.stylize.strobe",
|
||||
&[("strobeDuration", n(0.5)), ("strobeOperator", e(1)), ("strobeColor", c(0.2, 0.5, 0.9)), ("blendWithOriginal", n(30.0))],
|
||||
);
|
||||
effect_case("ec.stylize.strobe", &[("strobeDuration", n(0.5)), ("strobeOperator", e(4)), ("strobeColor", c(0.6, 0.1, 0.3))]);
|
||||
effect_case("ec.stylize.strobe", &[("strobeDuration", n(0.5)), ("strobe", e(1)), ("blendWithOriginal", n(40.0))]);
|
||||
effect_case("ec.stylize.strobe", &[("strobeDuration", n(0.1)), ("strobePeriod", n(0.2)), ("randomStrobeProbability", n(50.0))]);
|
||||
effect_case("ec.stylize.ccvignette", &[("amount", n(80.0))]);
|
||||
effect_case("ec.stylize.ccvignette", &[("amount", n(-60.0)), ("pinHighlights", n(50.0)), ("angleOfView", n(120.0)), ("center", pt(20.0, 15.0))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scatter_brush_strokes() {
|
||||
effect_case("ec.stylize.scatter", &[("amount", n(5.0))]);
|
||||
effect_case("ec.stylize.scatter", &[("amount", n(3.0)), ("grain", e(1))]);
|
||||
effect_case("ec.stylize.scatter", &[("amount", n(7.0)), ("grain", e(2)), ("randomizeEveryFrame", on())]);
|
||||
effect_case("ec.stylize.brushstrokes", &[]);
|
||||
effect_case(
|
||||
"ec.stylize.brushstrokes",
|
||||
&[("strokeAngle", n(40.0)), ("brushSize", n(3.0)), ("strokeLength", n(10.0)), ("paintSurface", e(2)), ("blendWithOriginal", n(20.0))],
|
||||
);
|
||||
effect_case("ec.stylize.brushstrokes", &[("paintSurface", e(1)), ("strokeDensity", n(0.6)), ("strokeRandomness", n(2.0)), ("brushSize", n(1.3))]);
|
||||
effect_case("ec.stylize.brushstrokes", &[("paintSurface", e(3)), ("strokeLength", n(25.0))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roughen_edges() {
|
||||
effect_case("ec.stylize.roughenedges", &[]);
|
||||
effect_case("ec.stylize.roughenedges", &[("edgeType", e(1)), ("border", n(4.0)), ("edgeColor", c(0.1, 0.6, 0.9))]);
|
||||
effect_case(
|
||||
"ec.stylize.roughenedges",
|
||||
&[
|
||||
("edgeType", e(5)),
|
||||
("evolutionOptions/cycleEvolution", on()),
|
||||
("evolutionOptions/cycle", n(2.0)),
|
||||
("evolution", n(500.0)),
|
||||
("stretch", n(2.0)),
|
||||
("complexity", n(3.0)),
|
||||
],
|
||||
);
|
||||
effect_case("ec.stylize.roughenedges", &[("edgeType", e(7)), ("border", n(0.0)), ("scale", n(60.0)), ("evolutionOptions/randomSeed", n(9.0))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tiles_kaleida_repetile() {
|
||||
effect_case("ec.stylize.motiontile", &[("tileWidth", n(50.0)), ("tileHeight", n(40.0))]);
|
||||
effect_case(
|
||||
"ec.stylize.motiontile",
|
||||
&[("outputWidth", n(150.0)), ("outputHeight", n(130.0)), ("mirrorEdges", on()), ("phase", n(70.0)), ("tileWidth", n(45.0))],
|
||||
);
|
||||
effect_case(
|
||||
"ec.stylize.motiontile",
|
||||
&[("horizontalPhaseShift", on()), ("phase", n(33.0)), ("tileCenter", pt(30.0, 17.0)), ("tileHeight", n(55.0)), ("outputWidth", n(80.0))],
|
||||
);
|
||||
effect_case("ec.stylize.cckaleida", &[]);
|
||||
effect_case("ec.stylize.cckaleida", &[("mirroring", e(2)), ("rotation", n(20.0)), ("size", n(150.0))]);
|
||||
effect_case("ec.stylize.ccrepetile", &[("expandRight", n(10.0)), ("expandUp", n(4.0))]);
|
||||
effect_case("ec.stylize.ccrepetile", &[("expandLeft", n(12.0)), ("expandDown", n(9.0)), ("tiling", e(1))]);
|
||||
effect_case("ec.stylize.ccrepetile", &[("expandLeft", n(30.0)), ("expandRight", n(30.0)), ("expandUp", n(20.0)), ("tiling", e(4))]);
|
||||
effect_case("ec.distort.cctiler", &[]);
|
||||
effect_case("ec.distort.cctiler", &[("scale", n(70.0)), ("blendWithOriginal", n(30.0)), ("center", pt(30.0, 18.0))]);
|
||||
effect_case("ec.distort.ccgriddler", &[("rotation", n(20.0))]);
|
||||
effect_case("ec.distort.ccgriddler", &[("cutTiles", on()), ("horizontalScale", n(120.0)), ("verticalScale", n(80.0)), ("tileSize", n(17.0))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mirror_offset_polar_spherize_corner_pin() {
|
||||
effect_case("ec.distort.mirror", &[("angle", n(30.0))]);
|
||||
effect_case("ec.distort.mirror", &[("angle", n(200.0)), ("center", pt(27.0, 19.0))]);
|
||||
effect_case("ec.distort.offset", &[("shift", pt(10.0, 5.0))]);
|
||||
effect_case("ec.distort.offset", &[("shift", pt(50.3, 20.7)), ("blend", n(40.0))]);
|
||||
effect_case("ec.distort.polar", &[("interpolation", n(100.0))]);
|
||||
effect_case("ec.distort.polar", &[("interpolation", n(60.0)), ("conversion", e(1))]);
|
||||
effect_case("ec.distort.spherize", &[("radius", n(20.0))]);
|
||||
effect_case("ec.distort.spherize", &[("radius", n(35.0)), ("center", pt(30.0, 25.0))]);
|
||||
effect_case("ec.distort.cornerpin", &[("ul", pt(5.0, 3.0)), ("ur", pt(60.0, 8.0)), ("lr", pt(66.0, 40.0)), ("ll", pt(2.0, 44.0))]);
|
||||
effect_case("ec.distort.cornerpin", &[("ul", pt(-8.0, 6.0)), ("ur", pt(75.0, -4.0)), ("lr", pt(50.0, 30.0)), ("ll", pt(12.0, 50.0))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optics_magnify() {
|
||||
effect_case("ec.distort.opticscompensation", &[("fieldOfView", n(60.0))]);
|
||||
effect_case("ec.distort.opticscompensation", &[("fieldOfView", n(90.0)), ("reverseLensDistortion", on()), ("fovOrientation", e(2))]);
|
||||
effect_case(
|
||||
"ec.distort.opticscompensation",
|
||||
&[("fieldOfView", n(110.0)), ("reverseLensDistortion", on()), ("resize", e(1)), ("viewCenter", pt(30.0, 20.0))],
|
||||
);
|
||||
effect_case("ec.distort.opticscompensation", &[("fieldOfView", n(120.0)), ("optimalPixels", on()), ("fovOrientation", e(1))]);
|
||||
effect_case("ec.distort.magnify", &[("size", n(15.0))]);
|
||||
effect_case(
|
||||
"ec.distort.magnify",
|
||||
&[("shape", e(1)), ("feather", n(6.0)), ("size", n(18.0)), ("scaling", e(1)), ("blendingMode", e(3)), ("magnification", n(270.0))],
|
||||
);
|
||||
effect_case("ec.distort.magnify", &[("scaling", e(2)), ("blendingMode", e(0)), ("size", n(20.0)), ("opacity", n(70.0))]);
|
||||
effect_case("ec.distort.magnify", &[("size", n(25.0)), ("center", pt(60.0, 10.0)), ("resizeLayer", on()), ("blendingMode", e(5))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cc_slant_smear_split() {
|
||||
effect_case("ec.distort.ccslant", &[("slant", n(30.0))]);
|
||||
effect_case("ec.distort.ccslant", &[("slant", n(-20.0)), ("stretching", on()), ("height", n(120.0))]);
|
||||
effect_case("ec.distort.ccsmear", &[]);
|
||||
effect_case("ec.distort.ccsmear", &[("reach", n(-40.0)), ("radius", n(20.0)), ("to", pt(40.0, 10.0))]);
|
||||
effect_case("ec.distort.ccsplit", &[]);
|
||||
effect_case("ec.distort.ccsplit2", &[("split1", n(30.0)), ("split2", n(80.0)), ("pointB", pt(60.0, 30.0))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn twirl_legacy_ripple_pulse_power_pin() {
|
||||
effect_case("ec.distort.twirllegacy", &[("angle", n(90.0))]);
|
||||
effect_case("ec.distort.twirllegacy", &[("angle", n(-250.0)), ("radius", n(80.0)), ("center", pt(30.0, 18.0))]);
|
||||
effect_case("ec.distort.ccripplepulse", &[("pulseLevel", n(80.0)), ("timeSpan", n(0.5))]);
|
||||
effect_case("ec.distort.ccripplepulse", &[("pulseLevel", n(-60.0)), ("amplitude", n(150.0)), ("renderBump", on()), ("center", pt(25.0, 20.0))]);
|
||||
effect_case("ec.distort.ccpowerpin", &[("topLeft", pt(5.0, 3.0)), ("bottomRight", pt(62.0, 38.0))]);
|
||||
effect_case("ec.distort.ccpowerpin", &[("topRight", pt(80.0, -6.0)), ("bottomLeft", pt(-4.0, 50.0)), ("expandLeft", n(20.0)), ("expandBottom", n(-10.0))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flo_motion() {
|
||||
effect_case("ec.distort.ccflomotion", &[]);
|
||||
effect_case("ec.distort.ccflomotion", &[("antialiasing", e(2)), ("tileEdges", on()), ("amount1", n(-150.0)), ("falloff", n(0.7))]);
|
||||
effect_case("ec.distort.ccflomotion", &[("antialiasing", e(0)), ("amount2", n(0.0)), ("knot1", pt(20.0, 30.0))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn liquify_render() {
|
||||
let mesh = "warp 20 50 0 0 0 10,10 20,15 30,20\ntwirlClockwise 24 80 0 0 0 40,25 42,27";
|
||||
effect_case("ec.distort.liquify", &[("distortionMesh", Value::Str(mesh.into()))]);
|
||||
effect_case(
|
||||
"ec.distort.liquify",
|
||||
&[("distortionMesh", Value::Str(mesh.into())), ("distortionPercentage", n(60.0)), ("distortionMeshOffset", pt(3.0, -2.0))],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn glow_operations_and_arbitrary_map() {
|
||||
effect_case("ec.stylize.glow", &[("glowOperation", e(2)), ("threshold", n(30.0)), ("radius", n(8.0))]);
|
||||
effect_case("ec.stylize.glow", &[("glowOperation", e(3)), ("colors", e(1)), ("intensity", n(2.0))]);
|
||||
effect_case("ec.stylize.glow", &[("colors", e(2)), ("arbitraryMap", Value::Str("0,0 0.5,0.8 1,1|0,0.2 1,0.6|".into())), ("threshold", n(20.0))]);
|
||||
effect_case("ec.stylize.glow", &[("colors", e(2)), ("arbitraryMap", Value::Str("0,1 1,0||0,0 1,1".into())), ("operation", e(1)), ("glowOperation", e(4))]);
|
||||
}
|
||||
|
||||
/// Texturize with a texture layer (Tile / Center / Stretch), composited.
|
||||
#[test]
|
||||
fn texturize_layer() {
|
||||
for depth in [BitDepth::Bpc8, BitDepth::Bpc32] {
|
||||
for placement in 0..3 {
|
||||
let mut s = Scene::new(depth);
|
||||
let bg = s.solid([0.15, 0.1, 0.2], 97, 61);
|
||||
s.push(bg);
|
||||
let mut tex = s.footage(50, 36);
|
||||
tex.switches.video = false;
|
||||
let tid = s.push(tex);
|
||||
let mut l = s.footage(70, 44);
|
||||
s.effect(
|
||||
&mut l,
|
||||
"ec.stylize.texturize",
|
||||
&[("textureLayer", Value::Layer(Some(tid.0))), ("texturePlacement", e(placement)), ("textureContrast", n(1.5)), ("lightDirection", n(60.0))],
|
||||
);
|
||||
set(&mut l, "transform/rotation", Value::Scalar(8.0));
|
||||
s.push(l);
|
||||
let label = format!("texturize {placement} {depth:?}");
|
||||
check(&label, compare_at(&s, opts(), Tick::ZERO), 0.0);
|
||||
check(&format!("{label} half"), compare_at(&s, RenderOpts { scale: 0.5, ..opts() }, Tick::ZERO), 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Transform's own shutter angle: the effect's rotation and position animate.
|
||||
#[test]
|
||||
fn transform_motion_blur() {
|
||||
for depth in [BitDepth::Bpc8, BitDepth::Bpc32] {
|
||||
let mut s = Scene::new(depth);
|
||||
let bg = s.solid([0.15, 0.1, 0.2], 97, 61);
|
||||
s.push(bg);
|
||||
let mut l = s.footage(70, 44);
|
||||
s.effect(&mut l, "ec.distort.transform", &[("useCompositionShutterAngle", Value::Bool(false)), ("shutterAngle", n(180.0))]);
|
||||
let rot = l.props.prop_mut("effects/#1/rotation").expect("transform rotation");
|
||||
rot.keys = vec![Keyframe::new(Tick::ZERO, Value::Scalar(0.0)), Keyframe::new(Tick::from_seconds_f64(1.0), Value::Scalar(120.0))];
|
||||
let pos = l.props.prop_mut("effects/#1/position").expect("transform position");
|
||||
pos.keys = vec![Keyframe::new(Tick::ZERO, pt(35.0, 22.0)), Keyframe::new(Tick::from_seconds_f64(1.0), pt(95.0, 40.0))];
|
||||
s.push(l);
|
||||
let label = format!("transform motion blur {depth:?}");
|
||||
check(&label, compare_at(&s, opts(), Tick::from_seconds_f64(0.3)), 0.0);
|
||||
check(&format!("{label} half"), compare_at(&s, RenderOpts { scale: 0.5, ..opts() }, Tick::from_seconds_f64(0.3)), 0.0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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**: 118 effects also run on the GPU compositor with identical results.
|
||||
- **GPU**: 150 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).
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
|||
| Color Balance | `ec.color.colorbalance` | 10 | GPU | 32 | Implemented |
|
||||
| Color Balance (HLS) | `ec.color.colorbalancehls` | 3 | GPU | 32 | Implemented |
|
||||
| Color Link | `ec.color.colorlink` | 6 | | 32 | Implemented |
|
||||
| Color Stabilizer | `ec.color.colorstabilizer` | 6 | | 32 | Implemented |
|
||||
| Color Stabilizer | `ec.color.colorstabilizer` | 6 | GPU | 32 | Implemented |
|
||||
| Colorama | `ec.color.colorama` | 20 | GPU | 32 | Implemented |
|
||||
| Curves | `ec.color.curves` | 0 | GPU | 32 | Implemented |
|
||||
| Equalize | `ec.color.equalize` | 2 | GPU | 32 | Implemented |
|
||||
|
|
@ -135,37 +135,37 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
|||
| CC Bend It | `ec.distort.ccbendit` | 4 | | 32 | Implemented |
|
||||
| CC Bender | `ec.distort.ccbender` | 5 | | 32 | Implemented |
|
||||
| CC Blobbylize | `ec.distort.ccblobbylize` | 15 | | 32 | Implemented |
|
||||
| CC Flo Motion | `ec.distort.ccflomotion` | 7 | | 32 | Implemented |
|
||||
| CC Griddler | `ec.distort.ccgriddler` | 5 | | 32 | Implemented |
|
||||
| CC Flo Motion | `ec.distort.ccflomotion` | 7 | GPU | 32 | Implemented |
|
||||
| CC Griddler | `ec.distort.ccgriddler` | 5 | GPU | 32 | Implemented |
|
||||
| CC Lens | `ec.distort.cclens` | 3 | GPU | 32 | Implemented |
|
||||
| CC Page Turn | `ec.distort.ccpageturn` | 7 | | 32 | Implemented |
|
||||
| CC Power Pin | `ec.distort.ccpowerpin` | 10 | | 32 | Implemented |
|
||||
| CC Ripple Pulse | `ec.distort.ccripplepulse` | 5 | | 32 | Implemented |
|
||||
| CC Slant | `ec.distort.ccslant` | 4 | | 32 | Implemented |
|
||||
| CC Smear | `ec.distort.ccsmear` | 4 | | 32 | Implemented |
|
||||
| CC Split | `ec.distort.ccsplit` | 3 | | 32 | Implemented |
|
||||
| CC Split 2 | `ec.distort.ccsplit2` | 4 | | 32 | Implemented |
|
||||
| CC Tiler | `ec.distort.cctiler` | 3 | | 32 | Implemented |
|
||||
| Corner Pin | `ec.distort.cornerpin` | 4 | | 32 | Implemented |
|
||||
| CC Power Pin | `ec.distort.ccpowerpin` | 10 | GPU | 32 | Implemented |
|
||||
| CC Ripple Pulse | `ec.distort.ccripplepulse` | 5 | GPU | 32 | Implemented |
|
||||
| CC Slant | `ec.distort.ccslant` | 4 | GPU | 32 | Implemented |
|
||||
| CC Smear | `ec.distort.ccsmear` | 4 | GPU | 32 | Implemented |
|
||||
| CC Split | `ec.distort.ccsplit` | 3 | GPU | 32 | Implemented |
|
||||
| CC Split 2 | `ec.distort.ccsplit2` | 4 | GPU | 32 | Implemented |
|
||||
| CC Tiler | `ec.distort.cctiler` | 3 | GPU | 32 | Implemented |
|
||||
| Corner Pin | `ec.distort.cornerpin` | 4 | GPU | 32 | Implemented |
|
||||
| Detail-preserving Upscale | `ec.distort.upscale` | 4 | | 32 | Implemented |
|
||||
| Displacement Map | `ec.distort.displacementmap` | 8 | GPU | 32 | Implemented |
|
||||
| Liquify | `ec.distort.liquify` | 12 | | 32 | Implemented |
|
||||
| Magnify | `ec.distort.magnify` | 10 | | 32 | Implemented |
|
||||
| Liquify | `ec.distort.liquify` | 12 | GPU | 32 | Implemented |
|
||||
| Magnify | `ec.distort.magnify` | 10 | GPU | 32 | Implemented |
|
||||
| Mesh Warp | `ec.distort.meshwarp` | 3 | GPU | 32 | Implemented |
|
||||
| Mirror | `ec.distort.mirror` | 2 | | 32 | Implemented |
|
||||
| Offset | `ec.distort.offset` | 2 | | 32 | Implemented |
|
||||
| Optics Compensation | `ec.distort.opticscompensation` | 6 | | 32 | Implemented |
|
||||
| Polar Coordinates | `ec.distort.polar` | 2 | | 32 | Implemented |
|
||||
| Mirror | `ec.distort.mirror` | 2 | GPU | 32 | Implemented |
|
||||
| Offset | `ec.distort.offset` | 2 | GPU | 32 | Implemented |
|
||||
| Optics Compensation | `ec.distort.opticscompensation` | 6 | GPU | 32 | Implemented |
|
||||
| Polar Coordinates | `ec.distort.polar` | 2 | GPU | 32 | Implemented |
|
||||
| Puppet | `ec.distort.puppet` | 1 | | 32 | Implemented |
|
||||
| Reshape | `ec.distort.reshape` | 6 | | 32 | Implemented |
|
||||
| Ripple | `ec.distort.ripple` | 7 | GPU | 32 | Implemented |
|
||||
| Rolling Shutter Repair | `ec.distort.rollingshutterrepair` | 5 | | 32 | Implemented |
|
||||
| Smear | `ec.distort.smear` | 8 | | 32 | Implemented |
|
||||
| Spherize | `ec.distort.spherize` | 2 | | 32 | Implemented |
|
||||
| Spherize | `ec.distort.spherize` | 2 | GPU | 32 | Implemented |
|
||||
| Transform | `ec.distort.transform` | 12 | GPU | 32 | Implemented |
|
||||
| Turbulent Displace | `ec.distort.turbulentdisplace` | 11 | GPU | 32 | Implemented |
|
||||
| Twirl | `ec.distort.twirl` | 3 | GPU | 32 | Implemented |
|
||||
| Twirl (Legacy) | `ec.distort.twirllegacy` | 3 | | 32 | Implemented |
|
||||
| Twirl (Legacy) | `ec.distort.twirllegacy` | 3 | GPU | 32 | Implemented |
|
||||
| Warp | `ec.distort.warp` | 5 | | 32 | Implemented |
|
||||
| Warp Stabilizer | `ec.distort.warpstabilizer` | 20 | | 32 | Implemented |
|
||||
| Wave Warp | `ec.distort.wavewarp` | 7 | GPU | 32 | Implemented |
|
||||
|
|
@ -340,31 +340,31 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
|||
|
||||
| Effect | Id | Params | GPU | 32 | Status |
|
||||
|---|---|---:|:-:|:-:|---|
|
||||
| Brush Strokes | `ec.stylize.brushstrokes` | 7 | | 32 | Implemented |
|
||||
| Brush Strokes | `ec.stylize.brushstrokes` | 7 | GPU | 32 | Implemented |
|
||||
| CC Block Load | `ec.stylize.ccblockload` | 5 | | 32 | Implemented |
|
||||
| CC Burn Film | `ec.stylize.ccburnfilm` | 3 | | 32 | Implemented |
|
||||
| CC Glass | `ec.stylize.ccglass` | 17 | | 32 | Implemented |
|
||||
| CC HexTile | `ec.stylize.cchextile` | 5 | | 32 | Implemented |
|
||||
| CC Kaleida | `ec.stylize.cckaleida` | 4 | | 32 | Implemented |
|
||||
| CC Kaleida | `ec.stylize.cckaleida` | 4 | GPU | 32 | Implemented |
|
||||
| CC Mr. Smoothie | `ec.stylize.ccmrsmoothie` | 7 | | 32 | Implemented |
|
||||
| CC Plastic | `ec.stylize.ccplastic` | 18 | | 32 | Implemented |
|
||||
| CC RepeTile | `ec.stylize.ccrepetile` | 5 | | 32 | Implemented |
|
||||
| CC Threshold | `ec.stylize.ccthreshold` | 4 | | 32 | Implemented |
|
||||
| CC Threshold RGB | `ec.stylize.ccthresholdrgb` | 7 | | 32 | Implemented |
|
||||
| CC Vignette | `ec.stylize.ccvignette` | 4 | | 32 | Implemented |
|
||||
| CC RepeTile | `ec.stylize.ccrepetile` | 5 | GPU | 32 | Implemented |
|
||||
| CC Threshold | `ec.stylize.ccthreshold` | 4 | GPU | 32 | Implemented |
|
||||
| CC Threshold RGB | `ec.stylize.ccthresholdrgb` | 7 | GPU | 32 | Implemented |
|
||||
| CC Vignette | `ec.stylize.ccvignette` | 4 | GPU | 32 | Implemented |
|
||||
| Cartoon | `ec.stylize.cartoon` | 12 | | 32 | Implemented |
|
||||
| Color Emboss | `ec.stylize.coloremboss` | 4 | | 32 | Implemented |
|
||||
| Emboss | `ec.stylize.emboss` | 4 | GPU | 32 | Implemented |
|
||||
| Find Edges | `ec.stylize.findedges` | 2 | GPU | 32 | Implemented |
|
||||
| Glow | `ec.stylize.glow` | 14 | GPU | 32 | Implemented |
|
||||
| Mosaic | `ec.stylize.mosaic` | 3 | GPU | 32 | Implemented |
|
||||
| Motion Tile | `ec.stylize.motiontile` | 8 | | 32 | Implemented |
|
||||
| Posterize | `ec.stylize.posterize` | 1 | | 32 | Implemented |
|
||||
| Roughen Edges | `ec.stylize.roughenedges` | 13 | | 32 | Implemented |
|
||||
| Scatter | `ec.stylize.scatter` | 3 | | 32 | Implemented |
|
||||
| Strobe Light | `ec.stylize.strobe` | 7 | | 32 | Implemented |
|
||||
| Texturize | `ec.stylize.texturize` | 4 | | 32 | Implemented |
|
||||
| Threshold | `ec.stylize.threshold` | 1 | | 32 | Implemented |
|
||||
| Motion Tile | `ec.stylize.motiontile` | 8 | GPU | 32 | Implemented |
|
||||
| Posterize | `ec.stylize.posterize` | 1 | GPU | 32 | Implemented |
|
||||
| Roughen Edges | `ec.stylize.roughenedges` | 13 | GPU | 32 | Implemented |
|
||||
| Scatter | `ec.stylize.scatter` | 3 | GPU | 32 | Implemented |
|
||||
| Strobe Light | `ec.stylize.strobe` | 7 | GPU | 32 | Implemented |
|
||||
| Texturize | `ec.stylize.texturize` | 4 | GPU | 32 | Implemented |
|
||||
| Threshold | `ec.stylize.threshold` | 1 | GPU | 32 | Implemented |
|
||||
|
||||
## Text
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue