Files
test/effects/posterize.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

80 lines
2.1 KiB
Python

# /// script
# requires-python = ">=3.10"
# dependencies = ["numpy"]
# ///
"""
@effect posterize
@version 1.0.0
@author artdag
@description
Posterize / Color Quantization effect. Reduces the number of colors
to create a poster/cartoon look. Great for stylized visuals.
@param levels int
@range 2 32
@default 8
Number of color levels per channel. Lower = more stylized.
@param dither bool
@default false
Apply dithering to reduce color banding.
@example
(effect posterize :levels 4)
@example
;; Beat-reactive posterization
(effect posterize :levels (bind bass :range [2 16]))
"""
import numpy as np
def process_frame(frame: np.ndarray, params: dict, state: dict) -> tuple:
"""
Apply posterize effect to a video frame.
Args:
frame: Input frame as numpy array (H, W, 3) RGB uint8
params: Effect parameters
- levels: color levels per channel (default 8)
- dither: apply dithering (default False)
state: Persistent state dict
Returns:
Tuple of (processed_frame, new_state)
"""
levels = max(2, min(int(params.get("levels", 8)), 256))
dither = params.get("dither", False)
if state is None:
state = {}
step = 256 // levels
if dither:
# Floyd-Steinberg dithering
result = frame.astype(np.float32).copy()
h, w = result.shape[:2]
for y in range(h - 1):
for x in range(1, w - 1):
for c in range(3):
old_val = result[y, x, c]
new_val = np.round(old_val / step) * step
result[y, x, c] = new_val
error = old_val - new_val
# Distribute error to neighbors
result[y, x + 1, c] += error * 7 / 16
result[y + 1, x - 1, c] += error * 3 / 16
result[y + 1, x, c] += error * 5 / 16
result[y + 1, x + 1, c] += error * 1 / 16
return np.clip(result, 0, 255).astype(np.uint8), state
else:
# Simple quantization
quantized = (frame // step) * step
return quantized.astype(np.uint8), state