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:
115
effects/chromatic.py
Normal file
115
effects/chromatic.py
Normal file
@@ -0,0 +1,115 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = ["numpy", "opencv-python"]
|
||||
# ///
|
||||
"""
|
||||
@effect chromatic
|
||||
@version 1.0.0
|
||||
@author artdag
|
||||
|
||||
@description
|
||||
Chromatic aberration effect. Creates color fringing by offsetting
|
||||
RGB channels radially from the center (lens distortion simulation).
|
||||
|
||||
@param strength float
|
||||
@range 0 50
|
||||
@default 10
|
||||
Aberration strength. Bind to bass for reactive effect.
|
||||
|
||||
@param center_x float
|
||||
@range 0 1
|
||||
@default 0.5
|
||||
Aberration center X.
|
||||
|
||||
@param center_y float
|
||||
@range 0 1
|
||||
@default 0.5
|
||||
Aberration center Y.
|
||||
|
||||
@param radial bool
|
||||
@default true
|
||||
If true, aberration increases from center to edges.
|
||||
|
||||
@example
|
||||
(effect chromatic :strength 20)
|
||||
|
||||
@example
|
||||
;; Beat-reactive chromatic aberration
|
||||
(effect chromatic :strength (bind bass :range [0 30]))
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import cv2
|
||||
|
||||
|
||||
def process_frame(frame: np.ndarray, params: dict, state: dict) -> tuple:
|
||||
"""
|
||||
Apply chromatic aberration effect to a video frame.
|
||||
|
||||
Args:
|
||||
frame: Input frame as numpy array (H, W, 3) RGB uint8
|
||||
params: Effect parameters
|
||||
- strength: aberration amount (default 10)
|
||||
- center_x: center X 0-1 (default 0.5)
|
||||
- center_y: center Y 0-1 (default 0.5)
|
||||
- radial: increase from center (default True)
|
||||
state: Persistent state dict
|
||||
|
||||
Returns:
|
||||
Tuple of (processed_frame, new_state)
|
||||
"""
|
||||
strength = params.get("strength", 10)
|
||||
center_x = params.get("center_x", 0.5)
|
||||
center_y = params.get("center_y", 0.5)
|
||||
radial = params.get("radial", True)
|
||||
|
||||
if state is None:
|
||||
state = {}
|
||||
|
||||
if strength == 0:
|
||||
return frame, state
|
||||
|
||||
h, w = frame.shape[:2]
|
||||
r, g, b = frame[:, :, 0], frame[:, :, 1], frame[:, :, 2]
|
||||
|
||||
if radial:
|
||||
# Create distance-from-center map
|
||||
y_coords, x_coords = np.ogrid[:h, :w]
|
||||
cx, cy = w * center_x, h * center_y
|
||||
dist = np.sqrt((x_coords - cx)**2 + (y_coords - cy)**2)
|
||||
max_dist = np.sqrt(cx**2 + cy**2)
|
||||
dist_normalized = (dist / max_dist).astype(np.float32)
|
||||
|
||||
# Create coordinate maps for remapping
|
||||
map_x = np.tile(np.arange(w, dtype=np.float32), (h, 1))
|
||||
map_y = np.tile(np.arange(h, dtype=np.float32).reshape(-1, 1), (1, w))
|
||||
|
||||
# Direction from center
|
||||
dx = (map_x - cx) / (dist + 1e-6)
|
||||
dy = (map_y - cy) / (dist + 1e-6)
|
||||
|
||||
# Apply radial offset to red channel (outward)
|
||||
r_offset = strength * dist_normalized
|
||||
r_map_x = (map_x + dx * r_offset).astype(np.float32)
|
||||
r_map_y = (map_y + dy * r_offset).astype(np.float32)
|
||||
r_shifted = cv2.remap(r, r_map_x, r_map_y,
|
||||
cv2.INTER_LINEAR, borderMode=cv2.BORDER_REPLICATE)
|
||||
|
||||
# Apply radial offset to blue channel (inward)
|
||||
b_offset = -strength * dist_normalized
|
||||
b_map_x = (map_x + dx * b_offset).astype(np.float32)
|
||||
b_map_y = (map_y + dy * b_offset).astype(np.float32)
|
||||
b_shifted = cv2.remap(b, b_map_x, b_map_y,
|
||||
cv2.INTER_LINEAR, borderMode=cv2.BORDER_REPLICATE)
|
||||
|
||||
return np.stack([r_shifted, g, b_shifted], axis=-1).astype(np.uint8), state
|
||||
else:
|
||||
# Simple uniform offset
|
||||
offset = int(strength)
|
||||
M_r = np.float32([[1, 0, offset], [0, 1, 0]])
|
||||
M_b = np.float32([[1, 0, -offset], [0, 1, 0]])
|
||||
|
||||
r_shifted = cv2.warpAffine(r, M_r, (w, h), borderMode=cv2.BORDER_REPLICATE)
|
||||
b_shifted = cv2.warpAffine(b, M_b, (w, h), borderMode=cv2.BORDER_REPLICATE)
|
||||
|
||||
return np.stack([r_shifted, g, b_shifted], axis=-1).astype(np.uint8), state
|
||||
Reference in New Issue
Block a user