foobazdmx/animation.py

287 lines
No EOL
7.4 KiB
Python

import random
import colorsys
from time import time
from typing import Tuple
class Animation:
def __init__(self):
pass
def __str__(self):
return f"{type(self).__name__}"
def update(self, index, count):
return (0, 0 ,0)
def name(self):
return "None"
class Off(Animation):
def __init__(self):
super(Off, self).__init__()
def name(self):
return "off"
class Steady(Animation):
def __init__(self, color):
super(Steady, self).__init__()
(r, g, b) = color
self.r = r
self.g = g
self.b = b
def update(self, index, count):
return (self.r, self.g, self.b)
def __str__(self):
return f"{type(self).__name__}({self.r}, {self.g}, {self.b})"
def name(self):
return "steady"
class RandomSingle(Animation):
"""
by Max & Lightmoll (https://lght.ml)
from 2022-06-08
"""
def __init__(self, color):
super().__init__()
self.PERIOD = 4 * 30 #frames
self.color = self._rand_color()
self.last_colors = []
self.frame_counter = 0
def update(self, index:int, count:int) -> Tuple[int, int, int]:
if index == 0:
if ((self.frame_counter % self.PERIOD) == 0):
self.last_colors = []
for _ in range(count):
self.last_colors.append(self._rand_color())
self.frame_counter += 1
try:
return self.last_colors[index]
except IndexError:
print("INDEX ERROR: ", index, len(self.last_colors))
raise Exception(f"{index}, {len(self.last_colors)}")
def _rand_color(self):
return (
round(random.random() * 255),
round(random.random() * 255),
round(random.random() * 255)
)
def __str__(self):
return "randomsingle"
def name(self):
return str(self)
class TwoColor(Steady):
"""
by Max & Lightmoll (https://lght.ml)
from 2022-06-08
"""
def __init__(self, color):
super().__init__(color)
self.start_time = time()
self.PERIOD = 0.5 #s
self.COL_1 = color #input color
self.COL_2 = (255-self.r, 255-self.g, 255-self.b) #compl. color
#of lights
def update(self, index:int, count:int) -> Tuple[int, int, int]:
time_diff = time() - self.start_time
#r,g,b
color = (0,0,0)
if (self.PERIOD / 2 > (time_diff % self.PERIOD)):
color = self.COL_1
else:
color = self.COL_2
return color
def __str__(self):
return "twocolor"
def name(self):
return str(self)
class Caramelldansen(Steady):
"""
by Max & Lightmoll (https://lght.ml)
from 2022-06-08
"""
def __init__(self, color):
super().__init__(color)
self.start_time = time()
self.PERIOD = int(0.42 * 30) #frames
self.COLORS = [
(230,50,50),
(255,0,0),
(50,230,50),
(0,255,0),
(50,50,230),
(0,0,255)
]
"""
self.COLORS = [
(255,0,0),
(0,255,0),
(0,0,255)
]
"""
self.color_index = 0
self.frame_counter = 0
def update(self, index:int, count:int) -> Tuple[int, int, int]:
time_diff = round(time() - self.start_time)
#r,g,b
if index == 0:
self.frame_counter += 1
if (((self.frame_counter % self.PERIOD) == 0) and index == 0):
self.color_index += 1
if (self.color_index == len(self.COLORS)):
#self.frame_counter = 0 #prevent big numbers
self.color_index = 0
return self.COLORS[self.color_index]
def __str__(self):
# when is this endpoint called?
return "caramelldansen"
def name(self):
return str(self)
class FadeTo(Steady):
def __init__(self, color, t=2.0):
super(FadeTo, self).__init__(color)
self.t = t
self.start = time()
def update(self, index, count):
h = (time() - self.start) / self.t
h = min(h, 1.0)
return (int(self.r * h), int(self.g * h), int(self.b * h))
def __str__(self):
return f"{type(self).__name__}({self.r}, {self.g}, {self.b}, {self.t:.2f})"
def name(self):
return "fadeTo"
class RotatingRainbow(Animation):
def __init__(self, looptime=50.0):
super(RotatingRainbow, self).__init__()
self.looptime = looptime
pass
def update(self, index, count):
"""
One full round takes self.looptime seconds, each RGB is offset in a circle
:param index:
:param count:
:return:
"""
hue = (time() / self.looptime + (index + 0.0) / count) % 1.0
rgb = hsv_to_rgb(hue, 1, 1)
return rgb
def __str__(self):
return f"{type(self).__name__}"
def name(self):
return "rainbow"
class Chase(Steady):
def __init__(self, color, looptime=1.0):
super(Chase, self).__init__(color)
self.looptime = looptime
def update(self, index, count):
angle = (time() / self.looptime + (index + 0.0) / count) % 1.0
l = 1 - min(abs(angle - 1 / count) * .9, 1.0 / count) * count
# print(f"f({index}, {angle:.2f}) -> {l:.2f}")
return (int(self.r * l), int(self.g * l), int(self.b * l))
def __str__(self):
return f"{type(self).__name__}({self.r}, {self.g}, {self.b}, {self.looptime:.2f})"
def name(self):
return "chase"
class Hackertours(Steady):
"""
Base color yellow, with green wandering back and forth
"""
def __init__(self, color, base=(255,255,0), looptime=2.0):
super(Hackertours, self).__init__(color)
self.looptime = looptime
self.base = base
def update(self, index, count):
# angle is the position of the highlight on a circle, range [0, 1]
angle = (time() / self.looptime + (index + 0.0) / count) % 1.0
# map 0->1, 0.5->0, 1->1 to convert from circle to cylon
angle = abs(angle * 2 - 1)
l = 1 - min(abs(angle - 1 / count) * .9, 1.0 / count) * count
return (
int(self.r * l + self.base[0] * (1-l)),
int(self.g * l + self.base[1] * (1-l)),
int(self.b * l + self.base[2] * (1-l)))
def __str__(self):
return f"{type(self).__name__}({self.r}, {self.g}, {self.b}, {self.looptime:.2f})"
def name(self):
return "hackertours"
class ChaseRandom(Steady):
def __init__(self, color, looptime=1.0):
super(ChaseRandom, self).__init__(color)
self.looptime = looptime
def update(self, index, count):
angle = (time() / self.looptime + (index + 0.0) / count) % 1.0
l = 1 - min(abs(angle - 1 / count) * .9, 1.0 / count) * count
# print(f"f({index}, {angle:.2f}) -> {l:.2f}")
return (int(self.r * l), int(self.g * l), int(self.b * l))
def __str__(self):
return f"{type(self).__name__}({self.r}, {self.g}, {self.b}, {self.looptime:.2f})"
def name(self):
return "chase"
def hsv_to_rgb(h, s, v):
(r, g, b) = colorsys.hsv_to_rgb(h, s, v)
return [int(r * 255), int(g * 255), int(b * 255)]
def rgb_to_hsv(r, g, b):
return colorsys.rgb_to_hsv(r/255,g/255,b/255)