Files
test/effects/neon_glow.py
gilesb 406cc7c0c7 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>
2026-01-19 12:34:45 +00:00

131 lines
3.5 KiB
Python

# /// 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