Python/OpenCV proof of concept plus first native Swift port
Snapshot before the Python POC is removed. The POC (main.py, effects.py, panel.py, shaders.py, effects/*.glsl, flake.nix) is the reference for the native app under native/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
6591138bbf
27 changed files with 2076 additions and 0 deletions
12
native/effects/colorize.metal
Normal file
12
native/effects/colorize.metal
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// Recolours the picture by brightness with a smooth colour ramp.
|
||||
// order 10
|
||||
// slider amount 0.0 1.0 0.8
|
||||
// slider palette 0.0 1.0 0.0
|
||||
// slider contrast 0.5 3.0 1.2
|
||||
float4 effect(float2 uv, Inputs in) {
|
||||
float3 c = in.image.sample(in.smp, uv).rgb;
|
||||
float luma = pow(dot(c, float3(0.299, 0.587, 0.114)), in.p.contrast);
|
||||
float3 ramp = 0.5 + 0.5 * cos(6.2832 * (luma * 0.8 + in.p.palette + float3(0.55, 0.4, 0.2)));
|
||||
float3 mapped = ramp * (0.15 + 0.85 * luma);
|
||||
return float4(mix(c, mapped, in.p.amount), 1.0);
|
||||
}
|
||||
18
native/effects/halftone.metal
Normal file
18
native/effects/halftone.metal
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
// Halftone dots sized by brightness; dots turn hot orange wherever something moves.
|
||||
// order 70
|
||||
// slider cell 4.0 40.0 12.0
|
||||
// slider heat 0.0 20.0 8.0
|
||||
float4 effect(float2 uv, Inputs in) {
|
||||
float2 grid = in.resolution / in.p.cell;
|
||||
float2 centre = (floor(uv * grid) + 0.5) / grid;
|
||||
float3 c = in.image.sample(in.smp, centre).rgb;
|
||||
float luma = dot(c, float3(0.299, 0.587, 0.114));
|
||||
|
||||
float radius = 0.5 * sqrt(luma);
|
||||
float dist = length(fract(uv * grid) - 0.5);
|
||||
float mask = 1.0 - smoothstep(radius - 0.05, radius + 0.05, dist);
|
||||
|
||||
float moved = length(in.camera.sample(in.smp, centre).rgb - in.last.sample(in.smp, centre).rgb);
|
||||
float3 hot = mix(c, float3(1.0, 0.35, 0.1), clamp(moved * in.p.heat, 0.0, 1.0));
|
||||
return float4(hot * mask, 1.0);
|
||||
}
|
||||
13
native/effects/kaleidoscope.metal
Normal file
13
native/effects/kaleidoscope.metal
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// Folds the picture into mirrored wedges around the centre.
|
||||
// order 90
|
||||
// slider segments 2.0 16.0 6.0
|
||||
// slider spin -2.0 2.0 0.2
|
||||
float4 effect(float2 uv, Inputs in) {
|
||||
float2 shape = float2(in.resolution.x / in.resolution.y, 1.0);
|
||||
float2 c = (uv - 0.5) * shape;
|
||||
float wedge = 6.2832 / floor(in.p.segments);
|
||||
float a = atan2(c.y, c.x) + in.time * in.p.spin;
|
||||
a = abs(a - wedge * floor(a / wedge) - wedge * 0.5) - 1.5708; // fold into one mirrored wedge, pointing up
|
||||
float2 from = float2(cos(a), sin(a)) * length(c) / shape + 0.5;
|
||||
return float4(in.image.sample(in.smp, from).rgb, 1.0);
|
||||
}
|
||||
13
native/effects/liquid.metal
Normal file
13
native/effects/liquid.metal
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// Liquid trails: the previous output is dragged along the motion, swirled a little,
|
||||
// and slowly fades back to the live picture.
|
||||
// order 45
|
||||
// slider persistence 0.0 0.99 0.92
|
||||
// slider drag 0.0 6.0 2.5
|
||||
// slider swirl 0.0 0.01 0.002
|
||||
float4 effect(float2 uv, Inputs in) {
|
||||
float2 centred = uv - 0.5;
|
||||
float2 from = uv - in.flow.sample(in.smp, uv).xy * in.p.drag + float2(-centred.y, centred.x) * in.p.swirl;
|
||||
float3 trail = in.prev.sample(in.smp, from).rgb;
|
||||
float3 live = in.image.sample(in.smp, uv).rgb;
|
||||
return float4(mix(live, trail, in.p.persistence), 1.0);
|
||||
}
|
||||
20
native/effects/motion-glow.metal
Normal file
20
native/effects/motion-glow.metal
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// Whatever moves lights up and the glow lingers. The heat is carried from frame to frame
|
||||
// in the alpha channel of this effect's own previous output.
|
||||
// order 50
|
||||
// slider sensitivity 1.0 30.0 10.0
|
||||
// slider linger 0.5 0.99 0.93
|
||||
// slider dim 0.0 1.0 0.45
|
||||
// slider hue 0.0 1.0 0.07
|
||||
// hue 0..1 -> a fully saturated colour (0 red, 0.08 orange, 0.33 green, 0.66 blue)
|
||||
static float3 hueColor(float h) {
|
||||
return clamp(abs(fract(h + float3(0.0, 2.0 / 3.0, 1.0 / 3.0)) * 6.0 - 3.0) - 1.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
float4 effect(float2 uv, Inputs in) {
|
||||
float moved = length(in.camera.sample(in.smp, uv).rgb - in.last.sample(in.smp, uv).rgb);
|
||||
float fresh = smoothstep(0.3, 1.0, moved * in.p.sensitivity);
|
||||
float heat = max(in.prev.sample(in.smp, uv).a * in.p.linger, fresh);
|
||||
|
||||
float3 glow = hueColor(in.p.hue);
|
||||
return float4(in.image.sample(in.smp, uv).rgb * in.p.dim + glow * heat, heat);
|
||||
}
|
||||
38
native/effects/neon-edges.metal
Normal file
38
native/effects/neon-edges.metal
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// Glowing outlines with a slowly cycling colour.
|
||||
// order 20
|
||||
// slider detail 1.0 12.0 5.0
|
||||
// slider glow 0.0 6.0 2.5
|
||||
// slider source 0.0 1.0 0.35
|
||||
// slider hue 0.0 1.0 0.55
|
||||
// slider cycle 0.0 1.0 0.3
|
||||
// hue 0..1 -> a fully saturated colour (0 red, 0.08 orange, 0.33 green, 0.66 blue)
|
||||
static float3 hueColor(float h) {
|
||||
return clamp(abs(fract(h + float3(0.0, 2.0 / 3.0, 1.0 / 3.0)) * 6.0 - 3.0) - 1.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
static float luma(Inputs in, float2 uv) {
|
||||
return dot(in.image.sample(in.smp, uv).rgb, float3(0.299, 0.587, 0.114));
|
||||
}
|
||||
|
||||
// how strongly brightness changes around this point
|
||||
static float edge(Inputs in, float2 uv) {
|
||||
float2 t = 1.5 / in.resolution;
|
||||
float dx = luma(in, uv + float2(t.x, 0.0)) - luma(in, uv - float2(t.x, 0.0));
|
||||
float dy = luma(in, uv + float2(0.0, t.y)) - luma(in, uv - float2(0.0, t.y));
|
||||
return length(float2(dx, dy));
|
||||
}
|
||||
|
||||
float4 effect(float2 uv, Inputs in) {
|
||||
float sharp = smoothstep(0.1, 0.5, edge(in, uv) * in.p.detail);
|
||||
float halo = 0.0; // edges found in a ring around this pixel = a cheap glow
|
||||
for (int i = 0; i < 12; i++) {
|
||||
float a = 6.2832 * float(i) / 12.0;
|
||||
float2 dir = float2(cos(a), sin(a)) / in.resolution;
|
||||
halo += edge(in, uv + dir * 4.0) + edge(in, uv + dir * 9.0);
|
||||
}
|
||||
halo = halo / 24.0 * in.p.detail * in.p.glow;
|
||||
|
||||
float3 tint = hueColor(in.p.hue + in.time * in.p.cycle * 0.2);
|
||||
float3 base = in.image.sample(in.smp, uv).rgb * in.p.source;
|
||||
return float4(base + tint * (sharp + halo), 1.0);
|
||||
}
|
||||
22
native/effects/particles.metal
Normal file
22
native/effects/particles.metal
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// Particles spawn where you move and drift along with the motion.
|
||||
// order 30
|
||||
// on
|
||||
// slider brightness 0.0 3.0 1.4
|
||||
// slider glow 0.0 4.0 1.5
|
||||
// slider hue 0.0 1.0 0.08
|
||||
// hue 0..1 -> a fully saturated colour (0 red, 0.08 orange, 0.33 green, 0.66 blue)
|
||||
static float3 hueColor(float h) {
|
||||
return clamp(abs(fract(h + float3(0.0, 2.0 / 3.0, 1.0 / 3.0)) * 6.0 - 3.0) - 1.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
float4 effect(float2 uv, Inputs in) {
|
||||
float core = in.particles.sample(in.smp, uv).r;
|
||||
float halo = 0.0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
float a = 6.2832 * float(i) / 8.0;
|
||||
halo += in.particles.sample(in.smp, uv + float2(cos(a), sin(a)) * 5.0 / in.resolution).r;
|
||||
}
|
||||
float3 tint = hueColor(in.p.hue);
|
||||
float3 base = in.image.sample(in.smp, uv).rgb;
|
||||
return float4(base + tint * (core * in.p.brightness + halo / 8.0 * in.p.glow), 1.0);
|
||||
}
|
||||
16
native/effects/trails.metal
Normal file
16
native/effects/trails.metal
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// Feedback trails: the previous output is zoomed, turned and faded, so bright things leave streaks.
|
||||
// "push" smears the trails in the direction things are moving.
|
||||
// order 40
|
||||
// on
|
||||
// slider fade 0.5 0.995 0.92
|
||||
// slider zoom -0.04 0.04 0.012
|
||||
// slider spin -0.05 0.05 0.005
|
||||
// slider push 0.0 4.0 0.0
|
||||
float4 effect(float2 uv, Inputs in) {
|
||||
float2 c = uv - 0.5;
|
||||
float2 turned = float2(c.x * cos(in.p.spin) - c.y * sin(in.p.spin), c.x * sin(in.p.spin) + c.y * cos(in.p.spin));
|
||||
float2 from = turned * (1.0 - in.p.zoom) + 0.5 - in.flow.sample(in.smp, uv).xy * in.p.push;
|
||||
float3 trail = in.prev.sample(in.smp, from).rgb * in.p.fade;
|
||||
float3 live = in.image.sample(in.smp, uv).rgb;
|
||||
return float4(max(live, trail), 1.0);
|
||||
}
|
||||
13
native/effects/wave-shift.metal
Normal file
13
native/effects/wave-shift.metal
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// RGB split that ripples down the image and gets pushed around by your movement.
|
||||
// order 60
|
||||
// slider amount 0.0 0.05 0.012
|
||||
// slider waves 1.0 80.0 24.0
|
||||
// slider speed 0.0 10.0 3.0
|
||||
// slider react 0.0 3.0 1.0
|
||||
float4 effect(float2 uv, Inputs in) {
|
||||
float2 d = float2(in.p.amount * sin(uv.y * in.p.waves + in.time * in.p.speed), 0.0);
|
||||
d += in.flow.sample(in.smp, uv).xy * in.p.react;
|
||||
return float4(in.image.sample(in.smp, uv + d).r,
|
||||
in.image.sample(in.smp, uv).g,
|
||||
in.image.sample(in.smp, uv - d).b, 1.0);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue