# /// script # requires-python = ">=3.10" # dependencies = ["numpy", "opencv-python"] # /// """ @effect emboss @version 1.0.0 @author artdag @description Emboss / relief effect. Creates a 3D raised appearance by highlighting edges from a simulated light direction. Great for sculptural looks. @param strength float @range 0.5 3 @default 1.0 Emboss intensity. @param direction float @range 0 360 @default 135 Light direction in degrees. Bind to beat for rotating light. @param blend float @range 0 1 @default 0.3 Blend with original (0 = full emboss, 1 = original). @example (effect emboss :strength 1.5) @example ;; Rotating light direction (effect emboss :direction (bind beat_position :range [0 360])) """ import numpy as np import cv2 def process_frame(frame: np.ndarray, params: dict, state: dict) -> tuple: """ Apply emboss effect to a video frame. Args: frame: Input frame as numpy array (H, W, 3) RGB uint8 params: Effect parameters - strength: emboss intensity (default 1.0) - direction: light angle in degrees (default 135) - blend: mix with original (default 0.3) state: Persistent state dict (unused) Returns: Tuple of (processed_frame, new_state) """ strength = params.get("strength", 1.0) direction = params.get("direction", 135) blend = params.get("blend", 0.3) # Calculate kernel based on direction angle_rad = np.deg2rad(direction) dx = np.cos(angle_rad) dy = np.sin(angle_rad) # Create emboss kernel kernel = np.array([ [-strength * dy - strength * dx, -strength * dy, -strength * dy + strength * dx], [-strength * dx, 1, strength * dx], [strength * dy - strength * dx, strength * dy, strength * dy + strength * dx] ], dtype=np.float32) # Apply to grayscale gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY).astype(np.float32) embossed = cv2.filter2D(gray, -1, kernel) # Normalize embossed = embossed + 128 embossed = np.clip(embossed, 0, 255) # Convert to RGB embossed_rgb = cv2.cvtColor(embossed.astype(np.uint8), cv2.COLOR_GRAY2RGB) # Blend with original if blend > 0: result = frame.astype(np.float32) * blend + embossed_rgb.astype(np.float32) * (1 - blend) return np.clip(result, 0, 255).astype(np.uint8), state return embossed_rgb, state