# /// script # requires-python = ">=3.10" # dependencies = ["numpy"] # /// """ @effect vignette @version 1.0.0 @author artdag @description Vignette effect. Darkens the corners of the frame, drawing focus to the center. Classic cinematic look. @param strength float @range 0 1 @default 0.5 How dark the corners get (0 = none, 1 = black corners). @param radius float @range 0.5 2 @default 1.0 Size of the bright center area. Smaller = more vignette. @param softness float @range 0.1 1 @default 0.5 How gradual the falloff is. @param center_x float @range 0 1 @default 0.5 Center X position. @param center_y float @range 0 1 @default 0.5 Center Y position. @param color list @default [0, 0, 0] Vignette color (default black). @example (effect vignette :strength 0.6) @example ;; Off-center vignette (effect vignette :center_x 0.3 :center_y 0.3 :strength 0.7) """ import numpy as np def process_frame(frame: np.ndarray, params: dict, state: dict) -> tuple: """ Apply vignette effect to a video frame. Args: frame: Input frame as numpy array (H, W, 3) RGB uint8 params: Effect parameters - strength: darkness 0-1 (default 0.5) - radius: center size 0.5-2 (default 1.0) - softness: falloff gradient (default 0.5) - center_x: center X 0-1 (default 0.5) - center_y: center Y 0-1 (default 0.5) - color: RGB tuple (default [0,0,0]) state: Persistent state dict Returns: Tuple of (processed_frame, new_state) """ strength = np.clip(params.get("strength", 0.5), 0, 1) radius = max(0.5, min(params.get("radius", 1.0), 2)) softness = max(0.1, min(params.get("softness", 0.5), 1)) center_x = params.get("center_x", 0.5) center_y = params.get("center_y", 0.5) color = params.get("color", [0, 0, 0]) if state is None: state = {} if strength <= 0: return frame, state h, w = frame.shape[:2] # Calculate center in pixels cx = w * center_x cy = h * center_y # Create distance map from center y_coords, x_coords = np.ogrid[:h, :w] dist = np.sqrt((x_coords - cx)**2 + (y_coords - cy)**2) # Normalize distance max_dist = np.sqrt(cx**2 + cy**2) * radius # Create vignette mask normalized_dist = dist / max_dist # Apply softness to the falloff vignette_mask = 1 - np.clip((normalized_dist - (1 - softness)) / softness, 0, 1) * strength # Apply vignette if isinstance(color, (list, tuple)) and len(color) >= 3: vignette_color = np.array(color[:3], dtype=np.float32) else: vignette_color = np.array([0, 0, 0], dtype=np.float32) result = frame.astype(np.float32) # Blend toward vignette color based on mask for c in range(3): result[:, :, c] = result[:, :, c] * vignette_mask + vignette_color[c] * (1 - vignette_mask) return np.clip(result, 0, 255).astype(np.uint8), state