# /// script # requires-python = ">=3.10" # dependencies = ["numpy"] # /// """ @effect beam @version 1.0.0 @author artdag @description Beam effect. Creates animated light beams / lasers from a starting point to an ending point with glow effect. @param start_x float @range 0 1 @default 0 Beam start X position (0-1). @param start_y float @range 0 1 @default 0.5 Beam start Y position (0-1). @param end_x float @range 0 1 @default 1 Beam end X position (0-1). @param end_y float @range 0 1 @default 0.5 Beam end Y position (0-1). @param thickness float @range 1 50 @default 5 Beam core thickness in pixels. @param glow_radius float @range 0 100 @default 20 Outer glow size in pixels. @param color list @default [0, 255, 255] Beam color RGB (default cyan). @param intensity float @range 0 2 @default 1.0 Beam brightness. @param pulse bool @default false Enable pulsing animation. @param pulse_speed float @range 0.1 10 @default 2.0 Pulse animation speed. @example (effect beam :start_x 0 :start_y 0.5 :end_x 1 :end_y 0.5) @example ;; Reactive laser (effect beam :intensity (bind bass :range [0.5 2]) :color [255 0 0]) """ import numpy as np def process_frame(frame: np.ndarray, params: dict, state: dict) -> tuple: """ Apply beam effect to a video frame. Args: frame: Input frame as numpy array (H, W, 3) RGB uint8 params: Effect parameters state: Persistent state dict Returns: Tuple of (processed_frame, new_state) """ start_x = params.get("start_x", 0) start_y = params.get("start_y", 0.5) end_x = params.get("end_x", 1) end_y = params.get("end_y", 0.5) thickness = params.get("thickness", 5) glow_radius = params.get("glow_radius", 20) color = params.get("color", [0, 255, 255]) intensity = params.get("intensity", 1.0) pulse = params.get("pulse", False) pulse_speed = params.get("pulse_speed", 2.0) t = params.get("_time", 0) if state is None: state = {} h, w = frame.shape[:2] result = frame.copy().astype(np.float32) # Calculate beam endpoints in pixels x1, y1 = int(start_x * w), int(start_y * h) x2, y2 = int(end_x * w), int(end_y * h) # Apply pulse modulation if pulse: pulse_mod = 0.5 + 0.5 * np.sin(t * pulse_speed * 2 * np.pi) intensity = intensity * pulse_mod # Create coordinate grids y_coords, x_coords = np.mgrid[0:h, 0:w].astype(np.float32) # Calculate distance from each pixel to the line segment line_vec = np.array([x2 - x1, y2 - y1], dtype=np.float32) line_len = np.sqrt(line_vec[0]**2 + line_vec[1]**2) if line_len < 1: return frame, state line_unit = line_vec / line_len # Vector from start to each pixel px = x_coords - x1 py = y_coords - y1 # Project onto line proj_len = px * line_unit[0] + py * line_unit[1] proj_len = np.clip(proj_len, 0, line_len) # Closest point on line closest_x = x1 + proj_len * line_unit[0] closest_y = y1 + proj_len * line_unit[1] # Distance to closest point dist = np.sqrt((x_coords - closest_x)**2 + (y_coords - closest_y)**2) # Get beam color if isinstance(color, (list, tuple)) and len(color) >= 3: beam_color = np.array(color[:3], dtype=np.float32) else: beam_color = np.array([0, 255, 255], dtype=np.float32) # Core beam (bright center) core_mask = dist < thickness core_intensity = intensity * (1 - dist[core_mask] / max(1, thickness)) for c in range(3): result[core_mask, c] = np.clip( result[core_mask, c] + beam_color[c] * core_intensity, 0, 255 ) # Glow (fading outer region) glow_mask = (dist >= thickness) & (dist < thickness + glow_radius) glow_dist = dist[glow_mask] - thickness glow_intensity = intensity * 0.5 * (1 - glow_dist / max(1, glow_radius)) ** 2 for c in range(3): result[glow_mask, c] = np.clip( result[glow_mask, c] + beam_color[c] * glow_intensity, 0, 255 ) return result.astype(np.uint8), state