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>
This commit is contained in:
79
effects/posterize.py
Normal file
79
effects/posterize.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# /// 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
|
||||
Reference in New Issue
Block a user