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:
111
effects/outline.py
Normal file
111
effects/outline.py
Normal file
@@ -0,0 +1,111 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = ["numpy", "opencv-python"]
|
||||
# ///
|
||||
"""
|
||||
@effect outline
|
||||
@version 1.0.0
|
||||
@author artdag
|
||||
|
||||
@description
|
||||
Outline / Toon Edges effect. Extracts and displays edges as outlines,
|
||||
optionally with fill. Great for cartoon/comic book aesthetics.
|
||||
|
||||
@param edge_thickness int
|
||||
@range 1 10
|
||||
@default 2
|
||||
Thickness of outlines in pixels.
|
||||
|
||||
@param threshold float
|
||||
@range 20 300
|
||||
@default 100
|
||||
Edge detection sensitivity.
|
||||
|
||||
@param outline_color list
|
||||
@default [0, 0, 0]
|
||||
RGB color for outlines (default black).
|
||||
|
||||
@param fill_mode string
|
||||
@enum original solid transparent
|
||||
@default original
|
||||
What to show in non-edge areas:
|
||||
- original: keep source image
|
||||
- solid: fill with solid color
|
||||
- transparent: black background
|
||||
|
||||
@param fill_color list
|
||||
@default [255, 255, 255]
|
||||
RGB color for solid fill mode.
|
||||
|
||||
@example
|
||||
(effect outline :edge_thickness 3 :threshold 80)
|
||||
|
||||
@example
|
||||
;; White outlines on black
|
||||
(effect outline :outline_color [255 255 255] :fill_mode "transparent")
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import cv2
|
||||
|
||||
|
||||
def process_frame(frame: np.ndarray, params: dict, state: dict) -> tuple:
|
||||
"""
|
||||
Apply outline effect to a video frame.
|
||||
|
||||
Args:
|
||||
frame: Input frame as numpy array (H, W, 3) RGB uint8
|
||||
params: Effect parameters
|
||||
- edge_thickness: outline width (default 2)
|
||||
- threshold: edge sensitivity (default 100)
|
||||
- outline_color: RGB tuple (default [0,0,0])
|
||||
- fill_mode: original/solid/transparent (default original)
|
||||
- fill_color: RGB tuple for solid fill (default [255,255,255])
|
||||
state: Persistent state dict
|
||||
|
||||
Returns:
|
||||
Tuple of (processed_frame, new_state)
|
||||
"""
|
||||
thickness = max(1, min(int(params.get("edge_thickness", 2)), 10))
|
||||
threshold = params.get("threshold", 100)
|
||||
outline_color = params.get("outline_color", [0, 0, 0])
|
||||
fill_mode = params.get("fill_mode", "original")
|
||||
fill_color = params.get("fill_color", [255, 255, 255])
|
||||
|
||||
if state is None:
|
||||
state = {}
|
||||
|
||||
h, w = frame.shape[:2]
|
||||
|
||||
# Convert to grayscale
|
||||
gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
|
||||
|
||||
# Apply edge detection
|
||||
edges = cv2.Canny(gray, int(threshold * 0.5), int(threshold))
|
||||
|
||||
# Dilate edges for thickness
|
||||
if thickness > 1:
|
||||
kernel = np.ones((thickness, thickness), np.uint8)
|
||||
edges = cv2.dilate(edges, kernel, iterations=1)
|
||||
|
||||
# Create result based on fill mode
|
||||
if fill_mode == "original":
|
||||
result = frame.copy()
|
||||
elif fill_mode == "solid":
|
||||
if isinstance(fill_color, (list, tuple)) and len(fill_color) >= 3:
|
||||
result = np.full((h, w, 3), fill_color[:3], dtype=np.uint8)
|
||||
else:
|
||||
result = np.full((h, w, 3), 255, dtype=np.uint8)
|
||||
else: # transparent/none
|
||||
result = np.zeros((h, w, 3), dtype=np.uint8)
|
||||
|
||||
# Apply outline color where edges exist
|
||||
if isinstance(outline_color, (list, tuple)) and len(outline_color) >= 3:
|
||||
color = np.array(outline_color[:3], dtype=np.uint8)
|
||||
else:
|
||||
color = np.array([0, 0, 0], dtype=np.uint8)
|
||||
|
||||
edge_mask = edges > 0
|
||||
result[edge_mask] = color
|
||||
|
||||
return result, state
|
||||
Reference in New Issue
Block a user