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