Initial commit: video effects processing system
Add S-expression based video effects pipeline with modular effect definitions, constructs, and recipe files. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
130
effects/neon_glow.py
Normal file
130
effects/neon_glow.py
Normal file
@@ -0,0 +1,130 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = ["numpy", "opencv-python"]
|
||||
# ///
|
||||
"""
|
||||
@effect neon_glow
|
||||
@version 1.0.0
|
||||
@author artdag
|
||||
|
||||
@description
|
||||
Neon edge glow effect. Detects edges and applies a glowing colored outline.
|
||||
Great for cyberpunk/synthwave aesthetics synced to music.
|
||||
|
||||
@param glow_radius float
|
||||
@range 0 50
|
||||
@default 15
|
||||
Blur radius for the glow. Bind to bass for pulsing glow.
|
||||
|
||||
@param glow_intensity float
|
||||
@range 0.5 5
|
||||
@default 2.0
|
||||
Brightness multiplier for the glow.
|
||||
|
||||
@param edge_low float
|
||||
@range 10 200
|
||||
@default 50
|
||||
Lower threshold for edge detection.
|
||||
|
||||
@param edge_high float
|
||||
@range 50 300
|
||||
@default 150
|
||||
Upper threshold for edge detection.
|
||||
|
||||
@param color_r int
|
||||
@range 0 255
|
||||
@default 0
|
||||
Red component of glow color.
|
||||
|
||||
@param color_g int
|
||||
@range 0 255
|
||||
@default 255
|
||||
Green component of glow color.
|
||||
|
||||
@param color_b int
|
||||
@range 0 255
|
||||
@default 255
|
||||
Blue component of glow color.
|
||||
|
||||
@param background float
|
||||
@range 0 1
|
||||
@default 0.3
|
||||
How much of the original image shows through (0 = glow only).
|
||||
|
||||
@example
|
||||
(effect neon_glow :glow_radius 20 :color_r 255 :color_g 0 :color_b 255)
|
||||
|
||||
@example
|
||||
;; Pulsing cyan glow on bass
|
||||
(effect neon_glow :glow_radius (bind bass :range [5 30] :transform sqrt))
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import cv2
|
||||
|
||||
|
||||
def process_frame(frame: np.ndarray, params: dict, state: dict) -> tuple:
|
||||
"""
|
||||
Apply neon glow effect to a video frame.
|
||||
|
||||
Args:
|
||||
frame: Input frame as numpy array (H, W, 3) RGB uint8
|
||||
params: Effect parameters
|
||||
- glow_radius: blur radius (default 15)
|
||||
- glow_intensity: brightness (default 2.0)
|
||||
- edge_low: canny low threshold (default 50)
|
||||
- edge_high: canny high threshold (default 150)
|
||||
- color_r/g/b: glow color (default cyan 0,255,255)
|
||||
- background: original visibility (default 0.3)
|
||||
state: Persistent state dict (unused)
|
||||
|
||||
Returns:
|
||||
Tuple of (processed_frame, new_state)
|
||||
"""
|
||||
glow_radius = int(params.get("glow_radius", 15))
|
||||
glow_intensity = params.get("glow_intensity", 2.0)
|
||||
edge_low = int(params.get("edge_low", 50))
|
||||
edge_high = int(params.get("edge_high", 150))
|
||||
color_r = int(params.get("color_r", 0))
|
||||
color_g = int(params.get("color_g", 255))
|
||||
color_b = int(params.get("color_b", 255))
|
||||
background = params.get("background", 0.3)
|
||||
|
||||
h, w = frame.shape[:2]
|
||||
color = np.array([color_r, color_g, color_b], dtype=np.float32)
|
||||
|
||||
# Edge detection
|
||||
gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
|
||||
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
|
||||
edges = cv2.Canny(blurred, edge_low, edge_high)
|
||||
|
||||
# Create colored edge image
|
||||
glow_base = np.zeros((h, w, 3), dtype=np.float32)
|
||||
for c in range(3):
|
||||
glow_base[:, :, c] = edges.astype(np.float32) * (color[c] / 255.0)
|
||||
|
||||
# Apply blur for glow
|
||||
if glow_radius > 0:
|
||||
ksize = glow_radius * 2 + 1
|
||||
glow = cv2.GaussianBlur(glow_base, (ksize, ksize), 0)
|
||||
else:
|
||||
glow = glow_base
|
||||
|
||||
# Intensify
|
||||
glow = glow * glow_intensity
|
||||
|
||||
# Add sharp edges on top
|
||||
edge_layer = np.zeros((h, w, 3), dtype=np.float32)
|
||||
for c in range(3):
|
||||
edge_layer[:, :, c] = edges.astype(np.float32) * (color[c] / 255.0) * 255
|
||||
glow = np.maximum(glow, edge_layer)
|
||||
|
||||
# Blend with original
|
||||
if background > 0:
|
||||
a = frame.astype(np.float32) / 255.0 * background
|
||||
b = glow / 255.0
|
||||
result = (1 - (1 - a) * (1 - b)) * 255 # Screen blend
|
||||
else:
|
||||
result = glow
|
||||
|
||||
return np.clip(result, 0, 255).astype(np.uint8), state
|
||||
Reference in New Issue
Block a user