# /// script # requires-python = ">=3.10" # dependencies = ["numpy"] # /// """ @effect noise @version 1.0.0 @author artdag @description Noise effect. Adds various types of noise to the image including static, gaussian, salt & pepper, and more. @param intensity float @range 0 1 @default 0.2 Noise intensity. @param mode string @enum gaussian uniform salt_pepper scanline @default gaussian Type of noise: - gaussian: smooth normal distribution - uniform: flat random noise - salt_pepper: random black/white pixels - scanline: horizontal line noise @param colored bool @default false Use colored noise instead of monochrome. @param animate bool @default true Different noise each frame. @param seed int @default 42 Random seed for reproducible noise. @state rng DeterministicRNG Random number generator. @example (effect noise :intensity 0.3 :mode "gaussian") @example ;; Static TV noise (effect noise :intensity 0.5 :mode "uniform" :animate true) """ import numpy as np from pathlib import Path import sys # Import DeterministicRNG from same directory _effects_dir = Path(__file__).parent if str(_effects_dir) not in sys.path: sys.path.insert(0, str(_effects_dir)) from random import DeterministicRNG def process_frame(frame: np.ndarray, params: dict, state: dict) -> tuple: """ Apply noise 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) """ intensity = params.get("intensity", 0.2) mode = params.get("mode", "gaussian") colored = params.get("colored", False) animate = params.get("animate", True) seed = int(params.get("seed", 42)) if state is None: state = {} if intensity <= 0: return frame, state # Initialize RNG if "rng" not in state: state["rng"] = DeterministicRNG(seed) rng = state["rng"] h, w = frame.shape[:2] result = frame.astype(np.float32) if mode == "gaussian": # Gaussian noise if colored: noise = np.array([[[rng.gaussian(0, intensity * 50) for _ in range(3)] for _ in range(w)] for _ in range(h)]) else: noise_2d = np.array([[rng.gaussian(0, intensity * 50) for _ in range(w)] for _ in range(h)]) noise = np.stack([noise_2d, noise_2d, noise_2d], axis=-1) result = result + noise elif mode == "uniform": # Uniform random noise if colored: noise = np.array([[[rng.uniform(-intensity * 100, intensity * 100) for _ in range(3)] for _ in range(w)] for _ in range(h)]) else: noise_2d = np.array([[rng.uniform(-intensity * 100, intensity * 100) for _ in range(w)] for _ in range(h)]) noise = np.stack([noise_2d, noise_2d, noise_2d], axis=-1) result = result + noise elif mode == "salt_pepper": # Salt and pepper noise for y in range(h): for x in range(w): if rng.uniform() < intensity * 0.1: if rng.uniform() < 0.5: result[y, x] = [0, 0, 0] else: result[y, x] = [255, 255, 255] elif mode == "scanline": # Horizontal scanline noise for y in range(h): if rng.uniform() < intensity * 0.2: noise_val = rng.uniform(-intensity * 100, intensity * 100) result[y] = result[y] + noise_val return np.clip(result, 0, 255).astype(np.uint8), state