forked from genewildish/Mainline
- Move effects_plugins/ to engine/effects/plugins/ - Update imports in engine/app.py - Update imports in all test files - Follows capability-based deps architecture Closes #27
100 lines
2.9 KiB
Python
100 lines
2.9 KiB
Python
from engine.effects.types import EffectConfig, EffectContext, EffectPlugin
|
|
|
|
|
|
class TintEffect(EffectPlugin):
|
|
"""Tint effect that applies an RGB color overlay to the buffer.
|
|
|
|
Uses ANSI escape codes to tint text with the specified RGB values.
|
|
Supports transparency (0-100%) for blending.
|
|
|
|
Inlets:
|
|
- r: Red component (0-255)
|
|
- g: Green component (0-255)
|
|
- b: Blue component (0-255)
|
|
- a: Alpha/transparency (0.0-1.0, where 0.0 = fully transparent)
|
|
"""
|
|
|
|
name = "tint"
|
|
config = EffectConfig(enabled=True, intensity=1.0)
|
|
|
|
# Define inlet types for PureData-style typing
|
|
@property
|
|
def inlet_types(self) -> set:
|
|
from engine.pipeline.core import DataType
|
|
|
|
return {DataType.TEXT_BUFFER}
|
|
|
|
@property
|
|
def outlet_types(self) -> set:
|
|
from engine.pipeline.core import DataType
|
|
|
|
return {DataType.TEXT_BUFFER}
|
|
|
|
def process(self, buf: list[str], ctx: EffectContext) -> list[str]:
|
|
if not buf:
|
|
return buf
|
|
|
|
# Get tint values from effect params or sensors
|
|
r = self.config.params.get("r", 255)
|
|
g = self.config.params.get("g", 255)
|
|
b = self.config.params.get("b", 255)
|
|
a = self.config.params.get("a", 0.3) # Default 30% tint
|
|
|
|
# Clamp values
|
|
r = max(0, min(255, int(r)))
|
|
g = max(0, min(255, int(g)))
|
|
b = max(0, min(255, int(b)))
|
|
a = max(0.0, min(1.0, float(a)))
|
|
|
|
if a <= 0:
|
|
return buf
|
|
|
|
# Convert RGB to ANSI 256 color
|
|
ansi_color = self._rgb_to_ansi256(r, g, b)
|
|
|
|
# Apply tint with transparency effect
|
|
result = []
|
|
for line in buf:
|
|
if not line.strip():
|
|
result.append(line)
|
|
continue
|
|
|
|
# Check if line already has ANSI codes
|
|
if "\033[" in line:
|
|
# For lines with existing colors, wrap the whole line
|
|
result.append(f"\033[38;5;{ansi_color}m{line}\033[0m")
|
|
else:
|
|
# Apply tint to plain text lines
|
|
result.append(f"\033[38;5;{ansi_color}m{line}\033[0m")
|
|
|
|
return result
|
|
|
|
def _rgb_to_ansi256(self, r: int, g: int, b: int) -> int:
|
|
"""Convert RGB (0-255 each) to ANSI 256 color code."""
|
|
if r == g == b == 0:
|
|
return 16
|
|
if r == g == b == 255:
|
|
return 231
|
|
|
|
# Calculate grayscale
|
|
gray = int((0.299 * r + 0.587 * g + 0.114 * b) / 255 * 24) + 232
|
|
|
|
# Calculate color cube
|
|
ri = int(r / 51)
|
|
gi = int(g / 51)
|
|
bi = int(b / 51)
|
|
color = 16 + 36 * ri + 6 * gi + bi
|
|
|
|
# Use whichever is closer - gray or color
|
|
gray_dist = abs(r - gray)
|
|
color_dist = (
|
|
(r - ri * 51) ** 2 + (g - gi * 51) ** 2 + (b - bi * 51) ** 2
|
|
) ** 0.5
|
|
|
|
if gray_dist < color_dist:
|
|
return gray
|
|
return color
|
|
|
|
def configure(self, config: EffectConfig) -> None:
|
|
self.config = config
|