M13.9: gpu stylize and distort (posterize, threshold, CC Threshold / RGB, strobe, CC Vignette, scatter, brush strokes, roughen edges, texturize, motion tile, CC Kaleida, CC RepeTile, mirror, offset, polar, spherize, corner pin, optics compensation, magnify, CC Slant / Smear / Split / Split 2 / Tiler / Griddler, liquify; glow operations and arbitrary map; transform motion blur)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
47f61debcb
commit
2e5d35dcee
|
|
@ -87,6 +87,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.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();
|
||||
|
|
|
|||
|
|
@ -71,7 +71,8 @@ pub use color_fx::{
|
|||
};
|
||||
pub use color2::Curve;
|
||||
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};
|
||||
|
|
@ -79,6 +80,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;
|
||||
|
|
@ -809,7 +811,33 @@ pub const GPU_EFFECTS: &[&str] = &[
|
|||
// effectcraft-gpu fx_key (keying, matte, channel)
|
||||
//
|
||||
// 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",
|
||||
// effectcraft-gpu fx_noise (noise, blur, time)
|
||||
//
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -1,19 +1,889 @@
|
|||
//! GPU effects, stylize and distort family (kernels in `shaders/fx_stylize.wgsl`, every entry point prefixed
|
||||
//! `fxs_`): the CPU effects' exact steps with the pixel loops as compute kernels.
|
||||
//!
|
||||
//! Parameter conversions, padding, output geometry and anything the CPU computes in f64 with a
|
||||
//! floor or a wrap (tile indices, wrapped coordinates, brush cells) are computed here exactly as
|
||||
//! the CPU does and uploaded as per-column / per-row / per-cell tables; the kernels do the
|
||||
//! per-pixel work. Glow's other Glow Operations / Arbitrary Map and Transform's shutter-angle
|
||||
//! motion blur extend the base kernels in `effects.rs` (see [`glow_extra`], [`transform_blur`]).
|
||||
|
||||
use std::f64::consts::{FRAC_PI_2, TAU};
|
||||
|
||||
use effectcraft_color::BlendMode;
|
||||
use effectcraft_effects::EffectCtx;
|
||||
use effectcraft_geom::{Mat3, vec2};
|
||||
use effectcraft_raster::Sampling;
|
||||
|
||||
use crate::context::Enc;
|
||||
use crate::effects::GBuf;
|
||||
use crate::context::{Enc, Params};
|
||||
use crate::effects::{GBuf, gaussian_blur};
|
||||
use crate::ops;
|
||||
|
||||
/// Compute entry points in `fx_stylize.wgsl`.
|
||||
pub(crate) const KERNELS: &[&str] = &[];
|
||||
pub(crate) const KERNELS: &[&str] = &[
|
||||
"fxs_point",
|
||||
"fxs_warp",
|
||||
"fxs_table",
|
||||
"fxs_griddler",
|
||||
"fxs_motiontile",
|
||||
"fxs_repetile",
|
||||
"fxs_magnify",
|
||||
"fxs_scatter",
|
||||
"fxs_brush",
|
||||
"fxs_roughen",
|
||||
"fxs_tex_luma",
|
||||
"fxs_texturize",
|
||||
"fxs_glow_map",
|
||||
"fxs_glow_op",
|
||||
"fxs_add",
|
||||
"fxs_div",
|
||||
];
|
||||
|
||||
/// Effect ids implemented here.
|
||||
pub(crate) const IDS: &[&str] = &[];
|
||||
pub(crate) const IDS: &[&str] = &[
|
||||
"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",
|
||||
];
|
||||
|
||||
/// 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> {
|
||||
let _ = (e, id, ctx, b);
|
||||
None
|
||||
match id {
|
||||
"ec.stylize.posterize" => posterize(e, ctx, b),
|
||||
"ec.stylize.threshold" => threshold(e, ctx, b),
|
||||
"ec.stylize.ccthreshold" => cc_threshold(e, ctx, b),
|
||||
"ec.stylize.ccthresholdrgb" => cc_threshold_rgb(e, ctx, b),
|
||||
"ec.stylize.strobe" => strobe(e, ctx, b),
|
||||
"ec.stylize.ccvignette" => vignette(e, ctx, b),
|
||||
"ec.stylize.scatter" => scatter(e, ctx, b),
|
||||
"ec.stylize.brushstrokes" => brush_strokes(e, ctx, b),
|
||||
"ec.stylize.roughenedges" => roughen_edges(e, ctx, b),
|
||||
"ec.stylize.texturize" => texturize(e, ctx, b),
|
||||
"ec.stylize.motiontile" => motion_tile(e, ctx, b),
|
||||
"ec.stylize.cckaleida" => kaleida(e, ctx, b),
|
||||
"ec.stylize.ccrepetile" => repetile(e, ctx, b),
|
||||
"ec.distort.mirror" => mirror(e, ctx, b),
|
||||
"ec.distort.offset" => offset(e, ctx, b),
|
||||
"ec.distort.polar" => polar(e, ctx, b),
|
||||
"ec.distort.spherize" => spherize(e, ctx, b),
|
||||
"ec.distort.cornerpin" => corner_pin(e, ctx, b),
|
||||
"ec.distort.opticscompensation" => optics_compensation(e, ctx, b),
|
||||
"ec.distort.magnify" => magnify(e, ctx, b),
|
||||
"ec.distort.ccslant" => cc_slant(e, ctx, b),
|
||||
"ec.distort.ccsmear" => cc_smear(e, ctx, b),
|
||||
"ec.distort.ccsplit" => {
|
||||
let s = ctx.params.f("split") / 100.0;
|
||||
cc_split(e, ctx, b, s, s)
|
||||
}
|
||||
"ec.distort.ccsplit2" => cc_split(e, ctx, b, ctx.params.f("split1") / 100.0, ctx.params.f("split2") / 100.0),
|
||||
"ec.distort.cctiler" => cc_tiler(e, ctx, b),
|
||||
"ec.distort.ccgriddler" => cc_griddler(e, ctx, b),
|
||||
"ec.distort.liquify" => liquify(e, ctx, b),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run a same-size per-pixel kernel over the buffer.
|
||||
fn run(e: &mut Enc, entry: &str, p: &Params, mut b: GBuf, data: Option<&wgpu::Buffer>) -> Option<GBuf> {
|
||||
let out = e.image(b.img.width, b.img.height);
|
||||
e.pixels(entry, p, &b.img, None, &out, data);
|
||||
b.img = out;
|
||||
Some(b)
|
||||
}
|
||||
|
||||
/// util::layer_rect.
|
||||
fn layer_rect(ctx: &EffectCtx, b: &GBuf) -> (f64, f64, f64, f64) {
|
||||
(b.offset[0], b.offset[1], ctx.layer_size[0] * b.scale, ctx.layer_size[1] * b.scale)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- per-pixel (fxs_point)
|
||||
|
||||
fn point(e: &mut Enc, mode: u32, f: impl FnOnce(&mut Params), b: GBuf) -> Option<GBuf> {
|
||||
let mut p = Params::default();
|
||||
p.u[0][0] = mode;
|
||||
f(&mut p);
|
||||
run(e, "fxs_point", &p, b, None)
|
||||
}
|
||||
|
||||
fn posterize(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let lv = ctx.params.f("level").round().clamp(2.0, 255.0) as f32 - 1.0;
|
||||
point(e, 1, |p| p.f[0][0] = lv, b)
|
||||
}
|
||||
|
||||
fn threshold(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let t = ctx.params.f("level") as f32 / 255.0;
|
||||
point(e, 2, |p| p.f[0][0] = t, b)
|
||||
}
|
||||
|
||||
fn cc_threshold(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let t = ctx.params.f("threshold") as f32 / 255.0;
|
||||
let blend = ctx.params.f("blend") as f32 / 100.0;
|
||||
let (ch, inv) = (ctx.params.e("channel"), ctx.params.b("invert") as u32);
|
||||
point(
|
||||
e,
|
||||
3,
|
||||
|p| {
|
||||
p.u[0][1] = ch;
|
||||
p.u[0][2] = inv;
|
||||
p.f[0] = [t, blend, 0.0, 0.0];
|
||||
},
|
||||
b,
|
||||
)
|
||||
}
|
||||
|
||||
fn cc_threshold_rgb(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let t = [ctx.params.f("redThreshold"), ctx.params.f("greenThreshold"), ctx.params.f("blueThreshold")].map(|v| v as f32 / 255.0);
|
||||
let inv = [ctx.params.b("invertRed"), ctx.params.b("invertGreen"), ctx.params.b("invertBlue")].map(u32::from);
|
||||
let blend = ctx.params.f("blend") as f32 / 100.0;
|
||||
point(
|
||||
e,
|
||||
4,
|
||||
|p| {
|
||||
p.u[0] = [4, inv[0], inv[1], inv[2]];
|
||||
p.f[0] = [t[0], t[1], t[2], blend];
|
||||
},
|
||||
b,
|
||||
)
|
||||
}
|
||||
|
||||
fn strobe(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let on = effectcraft_effects::strobe_on(
|
||||
ctx.time,
|
||||
ctx.params.f("strobeDuration"),
|
||||
ctx.params.f("strobePeriod"),
|
||||
ctx.params.f("randomStrobeProbability") / 100.0,
|
||||
ctx.seed,
|
||||
);
|
||||
if !on {
|
||||
return Some(b);
|
||||
}
|
||||
let blend = (ctx.params.f("blendWithOriginal") as f32 / 100.0).clamp(0.0, 1.0);
|
||||
if ctx.params.e("strobe") == 1 {
|
||||
// Image::scale_alpha.
|
||||
if (blend - 1.0).abs() < 1e-7 {
|
||||
return Some(b);
|
||||
}
|
||||
return point(e, 6, |p| p.f[0][0] = blend, b);
|
||||
}
|
||||
let s = ctx.params.color("strobeColor");
|
||||
let op = ctx.params.e("strobeOperator");
|
||||
point(
|
||||
e,
|
||||
5,
|
||||
|p| {
|
||||
p.u[0][1] = op;
|
||||
p.f[0] = [s[0], s[1], s[2], blend];
|
||||
},
|
||||
b,
|
||||
)
|
||||
}
|
||||
|
||||
fn vignette(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let amt = ctx.params.f("amount") as f32 / 100.0;
|
||||
let fov = ctx.params.f("angleOfView").clamp(0.0, 179.0).to_radians();
|
||||
if amt == 0.0 || fov <= 0.0 {
|
||||
return Some(b);
|
||||
}
|
||||
let c = b.to_px(ctx.params.v2("center"));
|
||||
let pin = ctx.params.f("pinHighlights") as f32 / 100.0;
|
||||
let (lw, lh) = (ctx.layer_size[0] * b.scale, ctx.layer_size[1] * b.scale);
|
||||
let half_diag = (lw * lw + lh * lh).sqrt() * 0.5;
|
||||
let dist = half_diag.max(1.0) / (fov * 0.5).tan();
|
||||
point(
|
||||
e,
|
||||
7,
|
||||
|p| {
|
||||
p.f[0] = [c.0 as f32, c.1 as f32, dist as f32, amt];
|
||||
p.f[1][0] = pin;
|
||||
},
|
||||
b,
|
||||
)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- warps (fxs_warp)
|
||||
|
||||
fn warp(e: &mut Enc, mode: u32, f: impl FnOnce(&mut Params), b: GBuf, data: Option<&wgpu::Buffer>) -> Option<GBuf> {
|
||||
let mut p = Params::default();
|
||||
p.u[0][0] = mode;
|
||||
f(&mut p);
|
||||
run(e, "fxs_warp", &p, b, data)
|
||||
}
|
||||
|
||||
fn mirror(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let c = b.to_px(ctx.params.v2("center"));
|
||||
let a = ctx.params.f("angle").to_radians();
|
||||
warp(e, 1, |p| p.f[0] = [c.0 as f32, c.1 as f32, a.cos() as f32, a.sin() as f32], b, None)
|
||||
}
|
||||
|
||||
fn spherize(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let r = (ctx.params.f("radius") * b.scale).max(1.0);
|
||||
let c = b.to_px(ctx.params.v2("center"));
|
||||
warp(e, 2, |p| p.f[0] = [c.0 as f32, c.1 as f32, r as f32, 0.0], b, None)
|
||||
}
|
||||
|
||||
fn polar(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let amt = ctx.params.f("interpolation") / 100.0;
|
||||
let to_polar = ctx.params.e("conversion") == 1;
|
||||
let (w, h) = (b.img.width as f64, b.img.height as f64);
|
||||
let (cx, cy) = (w / 2.0, h / 2.0);
|
||||
let rmax = cx.min(cy);
|
||||
warp(
|
||||
e,
|
||||
3,
|
||||
|p| {
|
||||
p.u[0][1] = to_polar as u32;
|
||||
p.f[0] = [w as f32, h as f32, cx as f32, cy as f32];
|
||||
p.f[1] = [rmax as f32, amt as f32, 0.0, 0.0];
|
||||
},
|
||||
b,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn cc_slant(e: &mut Enc, ctx: &EffectCtx, mut b: GBuf) -> Option<GBuf> {
|
||||
let k = ctx.params.f("slant") / 100.0;
|
||||
let height = ctx.params.f("height") / 100.0;
|
||||
if k == 0.0 && (height - 1.0).abs() < 1e-12 {
|
||||
return Some(b);
|
||||
}
|
||||
let stretch = ctx.params.b("stretching");
|
||||
let xs = if stretch { 1.0 / (1.0 + k * k).sqrt() } else { 1.0 };
|
||||
let hinv = if height.abs() < 1e-6 { 1e6 } else { 1.0 / height };
|
||||
if !ctx.adjustment {
|
||||
let lean = (k * ctx.layer_size[1] * b.scale * height.max(1.0)).abs();
|
||||
let grow = ((height - 1.0).max(0.0) * ctx.layer_size[1] * b.scale).max(lean);
|
||||
b.pad(e, grow.ceil().min(4096.0) as u32)?;
|
||||
}
|
||||
let fl = b.to_px(ctx.params.v2("floor"));
|
||||
warp(
|
||||
e,
|
||||
4,
|
||||
|p| {
|
||||
p.f[0] = [fl.0 as f32, fl.1 as f32, hinv as f32, k as f32];
|
||||
p.f[1][0] = xs as f32;
|
||||
},
|
||||
b,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn cc_smear(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let from = b.to_px(ctx.params.v2("from"));
|
||||
let to = b.to_px(ctx.params.v2("to"));
|
||||
let reach = ctx.params.f("reach") / 100.0;
|
||||
let radius = (ctx.params.f("radius") * b.scale).max(0.5);
|
||||
let (dx, dy) = (to.0 - from.0, to.1 - from.1);
|
||||
let l2 = dx * dx + dy * dy;
|
||||
if l2 < 1e-12 || reach == 0.0 {
|
||||
return Some(b);
|
||||
}
|
||||
warp(
|
||||
e,
|
||||
5,
|
||||
|p| {
|
||||
p.f[0] = [from.0 as f32, from.1 as f32, dx as f32, dy as f32];
|
||||
p.f[1] = [l2 as f32, reach as f32, radius as f32, 0.0];
|
||||
},
|
||||
b,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
/// distort3::split_impl (CC Split / CC Split 2).
|
||||
fn cc_split(e: &mut Enc, ctx: &EffectCtx, b: GBuf, s1: f64, s2: f64) -> Option<GBuf> {
|
||||
if s1 == 0.0 && s2 == 0.0 {
|
||||
return Some(b);
|
||||
}
|
||||
let a = b.to_px(ctx.params.v2("pointA"));
|
||||
let c = b.to_px(ctx.params.v2("pointB"));
|
||||
let (dx, dy) = (c.0 - a.0, c.1 - a.1);
|
||||
let len = (dx * dx + dy * dy).sqrt();
|
||||
if len < 1e-6 {
|
||||
return Some(b);
|
||||
}
|
||||
let (ux, uy) = (dx / len, dy / len);
|
||||
warp(
|
||||
e,
|
||||
6,
|
||||
|p| {
|
||||
p.f[0] = [a.0 as f32, a.1 as f32, ux as f32, uy as f32];
|
||||
p.f[1] = [len as f32, s1 as f32, s2 as f32, (len * 0.5) as f32];
|
||||
},
|
||||
b,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn optics_compensation(e: &mut Enc, ctx: &EffectCtx, mut b: GBuf) -> Option<GBuf> {
|
||||
let fov = ctx.params.f("fieldOfView").clamp(0.0, 179.0).to_radians();
|
||||
if fov < 1e-6 {
|
||||
return Some(b);
|
||||
}
|
||||
let reverse = ctx.params.b("reverseLensDistortion");
|
||||
let (lx, ly, lw, lh) = layer_rect(ctx, &b);
|
||||
let r_ref = match ctx.params.e("fovOrientation") {
|
||||
1 => lh * 0.5,
|
||||
2 => (lw * lw + lh * lh).sqrt() * 0.5,
|
||||
_ => lw * 0.5,
|
||||
}
|
||||
.max(1.0);
|
||||
let half = fov * 0.5;
|
||||
let f = r_ref / half.tan();
|
||||
let optimal = ctx.params.b("optimalPixels");
|
||||
let resize = if optimal { 0 } else { ctx.params.e("resize") };
|
||||
let corners = [(lx, ly), (lx + lw, ly), (lx, ly + lh), (lx + lw, ly + lh)];
|
||||
if reverse && resize > 0 && !ctx.adjustment {
|
||||
let c = b.to_px(ctx.params.v2("viewCenter"));
|
||||
let rc = corners.iter().map(|p| ((p.0 - c.0).powi(2) + (p.1 - c.1).powi(2)).sqrt()).fold(0.0, f64::max);
|
||||
let th = rc / r_ref * half;
|
||||
let cap = lw.max(lh) * [0.0, 0.5, 1.5, 3.5][resize.min(3) as usize];
|
||||
let grow = if th < FRAC_PI_2 - 1e-3 { (f * th.tan() - rc).max(0.0) } else { cap };
|
||||
b.pad(e, grow.min(cap).min(4096.0).ceil() as u32)?;
|
||||
}
|
||||
let c = b.to_px(ctx.params.v2("viewCenter"));
|
||||
let fit = if optimal {
|
||||
let rc = corners.iter().map(|p| ((p.0 - c.0).powi(2) + (p.1 - c.1).powi(2)).sqrt()).fold(1.0, f64::max);
|
||||
if reverse {
|
||||
let th = (rc / r_ref * half).min(FRAC_PI_2 - 1e-3);
|
||||
(f * th.tan() / rc).max(1.0)
|
||||
} else {
|
||||
((rc / f).atan() / half * r_ref / rc).min(1.0)
|
||||
}
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
warp(
|
||||
e,
|
||||
7,
|
||||
|p| {
|
||||
p.u[0][1] = reverse as u32;
|
||||
p.f[0] = [c.0 as f32, c.1 as f32, fit as f32, f as f32];
|
||||
p.f[1] = [r_ref as f32, half as f32, 0.0, 0.0];
|
||||
},
|
||||
b,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn kaleida(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let c = b.to_px(ctx.params.v2("center"));
|
||||
let size = ctx.params.f("size").max(1.0) / 100.0;
|
||||
let mode = ctx.params.e("mirroring");
|
||||
let rot = ctx.params.f("rotation").to_radians();
|
||||
let n = [6.0, 8.0, 4.0, 12.0, 3.0][mode.min(4) as usize];
|
||||
let seg = TAU / n;
|
||||
warp(
|
||||
e,
|
||||
8,
|
||||
|p| {
|
||||
p.u[0][1] = (mode != 3) as u32;
|
||||
p.f[0] = [c.0 as f32, c.1 as f32, size as f32, rot as f32];
|
||||
p.f[1][0] = seg as f32;
|
||||
},
|
||||
b,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn liquify(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
// View Freeze Area Mask / View Mesh overlays render on the CPU (catalog CPU_ONLY_CONTROLS).
|
||||
if ctx.params.b("viewFreezeAreaMask") || ctx.params.b("viewMesh") {
|
||||
return None;
|
||||
}
|
||||
if ctx.params.s("distortionMesh").trim().is_empty() {
|
||||
return Some(b);
|
||||
}
|
||||
let pct = ctx.params.f("distortionPercentage") / 100.0;
|
||||
let mesh = effectcraft_effects::liquify_mesh(ctx);
|
||||
if mesh.nx < 2 || mesh.ny < 2 {
|
||||
return None;
|
||||
}
|
||||
let off = ctx.params.v2("distortionMeshOffset");
|
||||
let data: Vec<f32> = mesh.d.iter().flat_map(|d| [d[0] as f32, d[1] as f32]).collect();
|
||||
let buf = e.data(&data);
|
||||
let (scale, o) = (b.scale, b.offset);
|
||||
warp(
|
||||
e,
|
||||
9,
|
||||
|p| {
|
||||
p.u[0] = [9, mesh.nx as u32, mesh.ny as u32, 0];
|
||||
p.f[0] = [mesh.cell as f32, off[0] as f32, off[1] as f32, pct as f32];
|
||||
p.f[1] = [scale as f32, o[0] as f32, o[1] as f32, 0.0];
|
||||
},
|
||||
b,
|
||||
Some(&buf),
|
||||
)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- separable tables
|
||||
|
||||
/// Per-column `fx(x + 0.5)` then per-row `fy(y + 0.5)`, `k` values each.
|
||||
fn axis_table<const K: usize>(w: u32, h: u32, fx: impl Fn(f64) -> [f64; K], fy: impl Fn(f64) -> [f64; K]) -> Vec<f32> {
|
||||
let mut v = Vec::with_capacity((w + h) as usize * K);
|
||||
v.extend((0..w).flat_map(|x| fx(x as f64 + 0.5).map(|t| t as f32)));
|
||||
v.extend((0..h).flat_map(|y| fy(y as f64 + 0.5).map(|t| t as f32)));
|
||||
v
|
||||
}
|
||||
|
||||
fn table(e: &mut Enc, b: GBuf, data: &[f32], repeat: bool, blend: f32) -> Option<GBuf> {
|
||||
let mut p = Params::default();
|
||||
p.u[0] = [b.img.width, repeat as u32, 0, 0];
|
||||
p.f[0][0] = blend;
|
||||
let buf = e.data(data);
|
||||
run(e, "fxs_table", &p, b, Some(&buf))
|
||||
}
|
||||
|
||||
fn offset(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let shift = ctx.params.v2("shift");
|
||||
let (w, h) = (b.img.width as f64, b.img.height as f64);
|
||||
let blend = ctx.params.f("blend") as f32 / 100.0;
|
||||
let (cx, cy) = (w / 2.0, h / 2.0);
|
||||
let (sx, sy) = b.to_px(shift);
|
||||
let data = axis_table(b.img.width, b.img.height, |x| [(x - (sx - cx)).rem_euclid(w)], |y| [(y - (sy - cy)).rem_euclid(h)]);
|
||||
table(e, b, &data, false, blend.max(0.0))
|
||||
}
|
||||
|
||||
fn cc_tiler(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let s = (ctx.params.f("scale") / 100.0).max(0.001);
|
||||
let c = b.to_px(ctx.params.v2("center"));
|
||||
let blend = (ctx.params.f("blendWithOriginal") / 100.0) as f32;
|
||||
let (x0, y0, w, h) = layer_rect(ctx, &b);
|
||||
let (w, h) = (w.max(1.0), h.max(1.0));
|
||||
let data = axis_table(b.img.width, b.img.height, |x| [x0 + (c.0 + (x - c.0) / s - x0).rem_euclid(w)], |y| [y0 + (c.1 + (y - c.1) / s - y0).rem_euclid(h)]);
|
||||
table(e, b, &data, true, blend)
|
||||
}
|
||||
|
||||
fn cc_griddler(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let hs = (ctx.params.f("horizontalScale") / 100.0).max(0.001);
|
||||
let vs = (ctx.params.f("verticalScale") / 100.0).max(0.001);
|
||||
let rot = ctx.params.f("rotation").to_radians();
|
||||
let cut = ctx.params.b("cutTiles");
|
||||
let (x0, y0, lw, _) = layer_rect(ctx, &b);
|
||||
let t = (ctx.params.f("tileSize") / 100.0 * lw).max(1.0);
|
||||
if (hs - 1.0).abs() < 1e-9 && (vs - 1.0).abs() < 1e-9 && rot.abs() < 1e-9 && !cut {
|
||||
return Some(b);
|
||||
}
|
||||
let (sr, cr) = rot.sin_cos();
|
||||
let centre = |p: f64, o: f64| {
|
||||
let tc = o + (((p - o) / t).floor() + 0.5) * t;
|
||||
[tc, p - tc]
|
||||
};
|
||||
let data = axis_table(b.img.width, b.img.height, |x| centre(x, x0), |y| centre(y, y0));
|
||||
let mut p = Params::default();
|
||||
p.u[0] = [b.img.width, cut as u32, 0, 0];
|
||||
p.f[0] = [cr as f32, sr as f32, hs as f32, vs as f32];
|
||||
p.f[1][0] = (0.45 * t) as f32;
|
||||
let buf = e.data(&data);
|
||||
run(e, "fxs_griddler", &p, b, Some(&buf))
|
||||
}
|
||||
|
||||
fn motion_tile(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let s = b.scale;
|
||||
let (lw, lh) = (ctx.layer_size[0], ctx.layer_size[1]);
|
||||
if lw <= 0.0 || lh <= 0.0 {
|
||||
return Some(b);
|
||||
}
|
||||
let tc = ctx.params.v2("tileCenter");
|
||||
let tw = (lw * ctx.params.f("tileWidth") / 100.0).max(0.01);
|
||||
let th = (lh * ctx.params.f("tileHeight") / 100.0).max(0.01);
|
||||
let mirror = ctx.params.b("mirrorEdges");
|
||||
let phase = ctx.params.f("phase") / 360.0;
|
||||
let hshift = ctx.params.b("horizontalPhaseShift");
|
||||
let (nw, nh, noff) = if ctx.adjustment {
|
||||
(b.img.width, b.img.height, b.offset)
|
||||
} else {
|
||||
let ow = (lw * ctx.params.f("outputWidth") / 100.0).max(1.0 / s);
|
||||
let oh = (lh * ctx.params.f("outputHeight") / 100.0).max(1.0 / s);
|
||||
((ow * s).round().max(1.0) as u32, (oh * s).round().max(1.0) as u32, [(ow - lw) * 0.5 * s, (oh - lh) * 0.5 * s])
|
||||
};
|
||||
if !e.g.fits(nw, nh) {
|
||||
return None;
|
||||
}
|
||||
let split = |t: f64| [t.floor(), t - t.floor()];
|
||||
let data = axis_table(nw, nh, |x| split(((x - noff[0]) / s - tc[0]) / tw + 0.5), |y| split(((y - noff[1]) / s - tc[1]) / th + 0.5));
|
||||
let mut p = Params::default();
|
||||
p.u[0] = [nw, hshift as u32, mirror as u32, 0];
|
||||
p.f[0] = [phase as f32, (lw * s) as f32, (lh * s) as f32, 0.0];
|
||||
p.f[1] = [b.offset[0] as f32, b.offset[1] as f32, 0.0, 0.0];
|
||||
let buf = e.data(&data);
|
||||
let out = e.image(nw, nh);
|
||||
e.pixels("fxs_motiontile", &p, &b.img, None, &out, Some(&buf));
|
||||
Some(GBuf { img: out, offset: noff, scale: s })
|
||||
}
|
||||
|
||||
fn repetile(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let s = b.scale;
|
||||
let (r, l, d, u) = (
|
||||
ctx.params.f("expandRight").max(0.0) * s,
|
||||
ctx.params.f("expandLeft").max(0.0) * s,
|
||||
ctx.params.f("expandDown").max(0.0) * s,
|
||||
ctx.params.f("expandUp").max(0.0) * s,
|
||||
);
|
||||
if ctx.adjustment || r + l + d + u < 0.5 {
|
||||
return Some(b);
|
||||
}
|
||||
let (x0, y0, w, h) = layer_rect(ctx, &b);
|
||||
if w < 1.0 || h < 1.0 {
|
||||
return Some(b);
|
||||
}
|
||||
let (nw, nh) = ((w + l + r).round().max(1.0) as u32, (h + u + d).round().max(1.0) as u32);
|
||||
if !e.g.fits(nw, nh) {
|
||||
return None;
|
||||
}
|
||||
// stylize2::tile_coord.
|
||||
let tile = |v: f64, size: f64| {
|
||||
let i = (v / size).floor();
|
||||
[i, v - i * size]
|
||||
};
|
||||
let data = axis_table(nw, nh, |x| tile(x - l, w), |y| tile(y - u, h));
|
||||
let mut p = Params::default();
|
||||
p.u[0] = [nw, ctx.params.e("tiling"), 0, 0];
|
||||
p.f[0] = [x0 as f32, y0 as f32, w as f32, h as f32];
|
||||
let buf = e.data(&data);
|
||||
let out = e.image(nw, nh);
|
||||
e.pixels("fxs_repetile", &p, &b.img, None, &out, Some(&buf));
|
||||
Some(GBuf { img: out, offset: [l, u], scale: s })
|
||||
}
|
||||
|
||||
fn magnify(e: &mut Enc, ctx: &EffectCtx, mut b: GBuf) -> Option<GBuf> {
|
||||
let square = ctx.params.e("shape") == 1;
|
||||
let c = b.to_px(ctx.params.v2("center"));
|
||||
let mag = (ctx.params.f("magnification") / 100.0).max(0.01);
|
||||
let link = ctx.params.e("link");
|
||||
let mut size = ctx.params.f("size") * b.scale;
|
||||
let mut feather = ctx.params.f("feather") * b.scale;
|
||||
if link >= 1 {
|
||||
size *= mag;
|
||||
}
|
||||
if link == 2 {
|
||||
feather *= mag;
|
||||
}
|
||||
let op = (ctx.params.f("opacity") / 100.0) as f32;
|
||||
let mode = ctx.params.e("blendingMode") as usize;
|
||||
let blend = effectcraft_effects::MAGNIFY_MODES.get(mode.wrapping_sub(1)).copied();
|
||||
if ctx.params.b("resizeLayer") && link == 0 && !ctx.adjustment {
|
||||
let (lx, ly, lw, lh) = layer_rect(ctx, &b);
|
||||
let reach = size.max(0.0);
|
||||
let over = [lx - (c.0 - reach), (c.0 + reach) - (lx + lw), ly - (c.1 - reach), (c.1 + reach) - (ly + lh)].into_iter().fold(0.0, f64::max);
|
||||
b.pad(e, over.min(4096.0).ceil() as u32)?;
|
||||
}
|
||||
let c = b.to_px(ctx.params.v2("center"));
|
||||
let axis = |p: f64, c: f64| {
|
||||
let d = p - c;
|
||||
let sp = c + d / mag;
|
||||
[d, sp, sp.floor() + 0.5]
|
||||
};
|
||||
let data = axis_table(b.img.width, b.img.height, |x| axis(x, c.0), |y| axis(y, c.1));
|
||||
let (kind, mode_id) = match blend {
|
||||
None => (0, 0),
|
||||
Some(BlendMode::Normal) => (1, 0),
|
||||
Some(m) => (2, ops::mode_id(m)),
|
||||
};
|
||||
let mut p = Params::default();
|
||||
p.u[0] = [b.img.width, square as u32, ctx.params.e("scaling"), kind];
|
||||
p.u[1] = [mode_id, ctx.seed ^ 0x3a61, 0, 0];
|
||||
p.f[0] = [size as f32, feather as f32, op, mag as f32];
|
||||
p.f[1][0] = (size - feather) as f32;
|
||||
let buf = e.data(&data);
|
||||
run(e, "fxs_magnify", &p, b, Some(&buf))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- stylize2.rs
|
||||
|
||||
fn scatter(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let amt = ctx.params.f("amount") * b.scale;
|
||||
if amt <= 0.0 {
|
||||
return Some(b);
|
||||
}
|
||||
let seed = if ctx.params.b("randomizeEveryFrame") { ctx.seed ^ ((ctx.time * 1000.0) as u32).wrapping_mul(0x9e37_79b9) } else { ctx.seed };
|
||||
let mut p = Params::default();
|
||||
p.u[0] = [ctx.params.e("grain"), seed, 0, 0];
|
||||
p.f[0][0] = amt as f32;
|
||||
run(e, "fxs_scatter", &p, b, None)
|
||||
}
|
||||
|
||||
fn brush_strokes(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let ang = ctx.params.f("strokeAngle");
|
||||
let size = (ctx.params.f("brushSize") * b.scale).max(0.5);
|
||||
let len = ctx.params.f("strokeLength") * b.scale;
|
||||
let density = ctx.params.f("strokeDensity") as f32;
|
||||
let rnd = ctx.params.f("strokeRandomness");
|
||||
let blend = ctx.params.f("blendWithOriginal") as f32 / 100.0;
|
||||
let seed = ctx.seed ^ 0xb705;
|
||||
let cell = (size * 2.0).max(1.0);
|
||||
let (w, h) = (b.img.width, b.img.height);
|
||||
let cell_of = |i: u32| (i as f64 / cell).floor() as i64 as u32;
|
||||
let (ncx, ncy) = (cell_of(w.saturating_sub(1)) + 1, cell_of(h.saturating_sub(1)) + 1);
|
||||
if (ncx as u64 * ncy as u64 * 6 + (w + h) as u64) >= 1 << 24 {
|
||||
return None;
|
||||
}
|
||||
let mut data: Vec<f32> = (0..w).map(|x| cell_of(x) as f32).chain((0..h).map(|y| cell_of(y) as f32)).collect();
|
||||
data.reserve((ncx * ncy * 6) as usize);
|
||||
for cy in 0..ncy {
|
||||
for cx in 0..ncx {
|
||||
let hh = |k: u32| effectcraft_raster::hash_noise(cx.wrapping_add(k.wrapping_mul(7919)), cy, seed) as f64;
|
||||
let a = (ang + (hh(0) - 0.5) * rnd * 60.0).to_radians();
|
||||
let l = len * (0.5 + hh(1));
|
||||
let (dx, dy) = (a.sin(), -a.cos());
|
||||
let jit = (hh(2) - 0.5) * size;
|
||||
let n = l.ceil().clamp(1.0, 32.0);
|
||||
let m = if (hh(3) as f32) < density.min(1.0) { 1.0 } else { 0.0 };
|
||||
data.extend([dx as f32, dy as f32, l as f32, jit as f32, n as f32, m]);
|
||||
}
|
||||
}
|
||||
let mut p = Params::default();
|
||||
p.u[0] = [w, ncx, ctx.params.e("paintSurface"), h];
|
||||
p.f[0][0] = blend;
|
||||
let buf = e.data(&data);
|
||||
run(e, "fxs_brush", &p, b, Some(&buf))
|
||||
}
|
||||
|
||||
fn roughen_edges(e: &mut Enc, ctx: &EffectCtx, mut b: GBuf) -> Option<GBuf> {
|
||||
let kind = ctx.params.e("edgeType");
|
||||
let ec = ctx.params.color("edgeColor");
|
||||
let border = ctx.params.f("border") * b.scale;
|
||||
let sharp = ctx.params.f("edgeSharpness").max(0.01) as f32;
|
||||
let infl = ctx.params.f("fractalInfluence") as f32;
|
||||
let scale = (ctx.params.f("scale") * b.scale * 0.1).max(0.5) as f32;
|
||||
let (ox, oy) = b.to_px(ctx.params.v2("offset"));
|
||||
let oct = ctx.params.f("complexity") as f32;
|
||||
let evo = ctx.params.f("evolution") as f32 / 360.0;
|
||||
let st = ctx.params.f("stretch") as f32;
|
||||
let (sx, sy) = (1.0 + st.max(0.0), 1.0 + (-st).max(0.0));
|
||||
let cycle = ctx.params.b("evolutionOptions/cycleEvolution").then(|| ctx.params.f("evolutionOptions/cycle").round().max(1.0) as f32);
|
||||
let (evo, evo_w, c) = match cycle {
|
||||
Some(c) => {
|
||||
let ev = evo.rem_euclid(c);
|
||||
(ev, ev / c, c)
|
||||
}
|
||||
None => (evo, 0.0, 0.0),
|
||||
};
|
||||
let (fm, im, sm, colored) = match kind {
|
||||
1 => (1.0, 1.0, 1.0, true),
|
||||
2 => (1.0, 1.0, 3.0, false),
|
||||
3 => (3.0, 1.0, 1.5, false),
|
||||
4 => (1.5, 1.5, 1.0, false),
|
||||
5 => (1.5, 1.5, 1.0, true),
|
||||
6 => (0.6, 1.2, 2.5, false),
|
||||
7 => (0.6, 1.2, 2.5, true),
|
||||
_ => (1.0, 1.0, 1.0, false),
|
||||
};
|
||||
let extra = if kind == 4 || kind == 5 { 1.0 } else { 0.0 };
|
||||
let seed = ctx.seed ^ 0x40f1 ^ (ctx.params.f("evolutionOptions/randomSeed").round() as i64 as u32).wrapping_mul(0x9e37_79b9);
|
||||
// util::gauss_plane of the alpha (edges repeated); the kernel reads .w.
|
||||
let d = if border > 0.05 { gaussian_blur(e, &b.img, border * 0.5, border * 0.5, true) } else { b.img.clone() };
|
||||
let mut p = Params::default();
|
||||
p.u[0] = [seed, cycle.is_some() as u32, colored as u32, 0];
|
||||
p.f[0] = [ox as f32, oy as f32, scale * sx, scale * sy];
|
||||
p.f[1] = [fm, evo, c, evo_w];
|
||||
p.f[2] = [oct + extra, infl, im, sharp];
|
||||
p.f[3] = [ec[0], ec[1], ec[2], sm];
|
||||
let out = e.image(b.img.width, b.img.height);
|
||||
e.pixels("fxs_roughen", &p, &b.img, Some(&d), &out, None);
|
||||
b.img = out;
|
||||
Some(b)
|
||||
}
|
||||
|
||||
fn texturize(e: &mut Enc, ctx: &EffectCtx, mut b: GBuf) -> Option<GBuf> {
|
||||
let ang = ctx.params.f("lightDirection").to_radians();
|
||||
let k = ctx.params.f("textureContrast") as f32;
|
||||
if k == 0.0 {
|
||||
return Some(b);
|
||||
}
|
||||
let Some(tex) = ctx.layer_param("textureLayer", true) else { return Some(b) };
|
||||
if tex.buf.img.is_empty() {
|
||||
return None;
|
||||
}
|
||||
// transition::place_layer, per column / row in the texture buffer's pixels.
|
||||
let (ls, os) = (ctx.layer_size, tex.size);
|
||||
let inv = 1.0 / b.scale.max(1e-9);
|
||||
let mode = ctx.params.e("texturePlacement");
|
||||
let place = |v: f64, k: usize| {
|
||||
let p = (v - b.offset[k]) * inv;
|
||||
let q = match mode {
|
||||
0 if os[0] >= 1.0 && os[1] >= 1.0 => p.rem_euclid(os[k]),
|
||||
1 => p + (os[k] - ls[k]) * 0.5,
|
||||
_ if ls[0] > 0.0 && ls[1] > 0.0 => p * os[k] / ls[k],
|
||||
_ => p,
|
||||
};
|
||||
[q * tex.buf.scale + tex.buf.offset[k]]
|
||||
};
|
||||
let data = axis_table(b.img.width, b.img.height, |x| place(x, 0), |y| place(y, 1));
|
||||
let timg = e.g.upload_image(&tex.buf.img)?;
|
||||
let mut p = Params::default();
|
||||
p.u[0][0] = b.img.width;
|
||||
let buf = e.data(&data);
|
||||
let luma = e.image(b.img.width, b.img.height);
|
||||
e.pixels("fxs_tex_luma", &p, &b.img, Some(&timg), &luma, Some(&buf));
|
||||
let s = 0.7 * b.scale;
|
||||
let hgt = gaussian_blur(e, &luma, s, s, true);
|
||||
let mut p = Params::default();
|
||||
p.f[0] = [ang.cos() as f32, -ang.sin() as f32, k, 0.0];
|
||||
let out = e.image(b.img.width, b.img.height);
|
||||
e.pixels("fxs_texturize", &p, &b.img, Some(&hgt), &out, None);
|
||||
b.img = out;
|
||||
Some(b)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- distort.rs
|
||||
|
||||
fn corner_pin(e: &mut Enc, ctx: &EffectCtx, mut b: GBuf) -> Option<GBuf> {
|
||||
let (w, h) = (ctx.layer_size[0].max(1.0), ctx.layer_size[1].max(1.0));
|
||||
let q = ["ul", "ur", "lr", "ll"].map(|k| {
|
||||
let p = b.to_px(ctx.params.v2(k));
|
||||
vec2(p.0, p.1)
|
||||
});
|
||||
let quad = Mat3::square_to_quad(q);
|
||||
let o = b.to_px([0.0, 0.0]);
|
||||
let m = quad * Mat3::scale(vec2(1.0 / (w * b.scale), 1.0 / (h * b.scale))) * Mat3::translate(vec2(-o.0, -o.1));
|
||||
let empty = e.image(b.img.width, b.img.height);
|
||||
b.img = ops::warp(e, &empty, &b.img, &m, Sampling::Bilinear, BlendMode::Normal, 1.0, 0, None);
|
||||
Some(b)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Glow / Transform extras
|
||||
|
||||
/// Glow settings the base kernel in `effects.rs` leaves to the CPU (another Glow Operation or
|
||||
/// the Arbitrary Map) and [`glow`] implements.
|
||||
pub(crate) fn glow_extra(ctx: &EffectCtx) -> bool {
|
||||
ctx.params.e("colors") == 2 || effectcraft_effects::glow_operation(ctx) != BlendMode::Add
|
||||
}
|
||||
|
||||
fn dims_xy(dim: u32) -> (f64, f64) {
|
||||
match dim {
|
||||
1 => (1.0, 0.0),
|
||||
2 => (0.0, 1.0),
|
||||
_ => (1.0, 1.0),
|
||||
}
|
||||
}
|
||||
|
||||
/// misc::glow with any Glow Operation and the Arbitrary Map.
|
||||
pub(crate) fn glow(e: &mut Enc, ctx: &EffectCtx, mut b: GBuf) -> Option<GBuf> {
|
||||
let thr = ctx.params.f("threshold") as f32 / 100.0;
|
||||
let radius = ctx.params.f("radius") * b.scale;
|
||||
let intensity = ctx.params.f("intensity") as f32;
|
||||
let colors = ctx.params.e("colors");
|
||||
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] = [(colors == 1) 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];
|
||||
let bright = e.image(b.img.width, b.img.height);
|
||||
if colors == 2 {
|
||||
// Arbitrary Map: Curves tables per channel (see effects::curves).
|
||||
let mut parts = ctx.params.s("arbitraryMap").split('|');
|
||||
let curves: Vec<Option<effectcraft_effects::Curve>> = (0..3).map(|_| parts.next().and_then(effectcraft_effects::Curve::parse)).collect();
|
||||
let mut data = vec![0.0f32];
|
||||
let mut offs = [-1.0f32; 3];
|
||||
for (i, c) in curves.iter().enumerate() {
|
||||
let Some(c) = c else { continue };
|
||||
let (xs, ys, ms, lut) = c.tables();
|
||||
offs[i] = data.len() as f32;
|
||||
data.push(xs.len() as f32);
|
||||
data.extend_from_slice(xs);
|
||||
data.extend_from_slice(ys);
|
||||
data.extend_from_slice(ms);
|
||||
data.extend_from_slice(lut);
|
||||
}
|
||||
p.f[1] = [offs[0], offs[1], offs[2], 0.0];
|
||||
let buf = e.data(&data);
|
||||
e.pixels("fxs_glow_map", &p, &b.img, None, &bright, Some(&buf));
|
||||
} else {
|
||||
p.f[1] = ctx.params.color("colorA");
|
||||
p.f[2] = ctx.params.color("colorB");
|
||||
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 * kx, s * ky, false);
|
||||
let operation = ctx.params.e("operation");
|
||||
let glow_op = effectcraft_effects::glow_operation(ctx);
|
||||
let mut p = Params::default();
|
||||
p.f[0][0] = intensity;
|
||||
let out = e.image(b.img.width, b.img.height);
|
||||
if operation == 0 && glow_op != BlendMode::Add {
|
||||
p.u[0][0] = ops::mode_id(glow_op);
|
||||
e.pixels("fxs_glow_op", &p, &b.img, Some(&blurred), &out, None);
|
||||
} else {
|
||||
p.u[0][0] = operation.min(2);
|
||||
e.pixels("glow_combine", &p, &b.img, Some(&blurred), &out, None);
|
||||
}
|
||||
b.img = out;
|
||||
Some(b)
|
||||
}
|
||||
|
||||
/// Transform renders with motion blur (a shutter and a host to read the parameters at other
|
||||
/// times): [`transform`] implements it, the base kernel in `effects.rs` the sharp case.
|
||||
pub(crate) fn transform_blur(ctx: &EffectCtx) -> bool {
|
||||
ctx.env.host.is_some() && effectcraft_effects::transform_shutter(ctx).is_some()
|
||||
}
|
||||
|
||||
/// distort::transform_matrix.
|
||||
fn transform_matrix(pr: &effectcraft_effects::Params, b: &GBuf) -> Mat3 {
|
||||
let anchor = b.to_px(pr.v2("anchor"));
|
||||
let pos = b.to_px(pr.v2("position"));
|
||||
let sh = pr.f("scaleHeight");
|
||||
let sw = if pr.b("uniform") { sh } else { pr.f("scaleWidth") };
|
||||
Mat3::translate(vec2(pos.0, pos.1))
|
||||
* Mat3::rotate_deg(pr.f("rotation"))
|
||||
* Mat3::skew_deg(pr.f("skew"), pr.f("skewAxis"))
|
||||
* Mat3::scale(vec2(sw / 100.0, sh / 100.0))
|
||||
* Mat3::translate(vec2(-anchor.0, -anchor.1))
|
||||
}
|
||||
|
||||
/// distort::transform's motion blur: the transform at Samples instants across the shutter,
|
||||
/// averaged.
|
||||
pub(crate) fn transform(e: &mut Enc, ctx: &EffectCtx, mut b: GBuf) -> Option<GBuf> {
|
||||
let (angle, phase, n) = effectcraft_effects::transform_shutter(ctx)?;
|
||||
let host = ctx.env.host?;
|
||||
let sampling = if ctx.params.e("sampling") == 1 { Sampling::Bicubic } else { Sampling::Bilinear };
|
||||
let fd = 1.0 / ctx.fps();
|
||||
let (w, h) = (b.img.width, b.img.height);
|
||||
let mut acc = e.image(w, h);
|
||||
let mut count = 0u32;
|
||||
for k in 0..n {
|
||||
let t = ctx.time + (phase + angle * k as f64 / (n - 1) as f64) / 360.0 * fd;
|
||||
let Some(pr) = host.params_at(t) else { continue };
|
||||
let op = pr.f("opacity") as f32 / 100.0;
|
||||
let empty = e.image(w, h);
|
||||
let one = ops::warp(e, &empty, &b.img, &transform_matrix(&pr, &b), sampling, BlendMode::Normal, op, 0, None);
|
||||
let sum = e.image(w, h);
|
||||
e.pixels("fxs_add", &Params::default(), &acc, Some(&one), &sum, None);
|
||||
acc = sum;
|
||||
count += 1;
|
||||
}
|
||||
if count == 0 {
|
||||
// The CPU renders the frame sharp.
|
||||
return None;
|
||||
}
|
||||
let mut p = Params::default();
|
||||
p.f[0][0] = count as f32;
|
||||
let out = e.image(w, h);
|
||||
e.pixels("fxs_div", &p, &acc, None, &out, None);
|
||||
b.img = out;
|
||||
Some(b)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,808 @@
|
|||
// 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
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- 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,206 @@
|
|||
//! 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 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**: 59 effects also run on the GPU compositor with identical results.
|
||||
- **GPU**: 86 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).
|
||||
|
||||
|
|
@ -136,32 +136,32 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
|||
| 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 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 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 |
|
||||
|
|
@ -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