Files
test/effects/bloom.py
gilesb 406cc7c0c7 Initial commit: video effects processing system
Add S-expression based video effects pipeline with modular effect
definitions, constructs, and recipe files.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 12:34:45 +00:00

109 lines
2.8 KiB
Python

# /// script
# requires-python = ">=3.10"
# dependencies = ["numpy", "opencv-python"]
# ///
"""
@effect bloom
@version 1.0.0
@author artdag
@description
Bloom effect. Creates a soft glow around bright areas, simulating
camera lens bloom. Great for dreamy or ethereal looks.
@param intensity float
@range 0 2
@default 0.5
Bloom brightness.
@param threshold int
@range 0 255
@default 200
Brightness threshold for bloom (pixels above this glow).
@param radius int
@range 1 50
@default 15
Blur radius for the glow.
@param soft_threshold bool
@default true
Use soft threshold (gradual) vs hard threshold.
@param color_tint list
@default [255, 255, 255]
Tint color for the bloom.
@example
(effect bloom :intensity 0.7 :threshold 180)
@example
;; Warm bloom
(effect bloom :intensity 0.6 :color_tint [255 200 150])
"""
import numpy as np
import cv2
def process_frame(frame: np.ndarray, params: dict, state: dict) -> tuple:
"""
Apply bloom effect to a video frame.
Args:
frame: Input frame as numpy array (H, W, 3) RGB uint8
params: Effect parameters
- intensity: bloom brightness (default 0.5)
- threshold: brightness cutoff (default 200)
- radius: blur radius (default 15)
- soft_threshold: gradual vs hard (default True)
- color_tint: RGB tint (default white)
state: Persistent state dict
Returns:
Tuple of (processed_frame, new_state)
"""
intensity = params.get("intensity", 0.5)
threshold = int(params.get("threshold", 200))
radius = max(1, int(params.get("radius", 15)))
soft_threshold = params.get("soft_threshold", True)
color_tint = params.get("color_tint", [255, 255, 255])
if state is None:
state = {}
if intensity <= 0:
return frame, state
# Convert to float
result = frame.astype(np.float32)
# Get brightness (luminance)
lum = 0.299 * result[:, :, 0] + 0.587 * result[:, :, 1] + 0.114 * result[:, :, 2]
# Create bloom mask
if soft_threshold:
# Soft threshold - gradual falloff
bloom_mask = np.clip((lum - threshold) / (255 - threshold + 1e-6), 0, 1)
else:
# Hard threshold
bloom_mask = (lum > threshold).astype(np.float32)
# Extract bright areas
bloom = result * bloom_mask[:, :, np.newaxis]
# Apply blur to create glow
ksize = radius * 2 + 1
bloom = cv2.GaussianBlur(bloom, (ksize, ksize), 0)
# Apply color tint
if isinstance(color_tint, (list, tuple)) and len(color_tint) >= 3:
tint = np.array(color_tint[:3], dtype=np.float32) / 255.0
for c in range(3):
bloom[:, :, c] *= tint[c]
# Add bloom to original (screen blend)
result = result + bloom * intensity
return np.clip(result, 0, 255).astype(np.uint8), state