162 lines
4.2 KiB
Python
Executable File
162 lines
4.2 KiB
Python
Executable File
# Render a box with up to three animations
|
|
#
|
|
import time
|
|
from icon import Icon
|
|
|
|
class AnimationScene:
|
|
"""Render animations from https://developer.lametric.com"""
|
|
|
|
def __init__(self, display, config):
|
|
self.display = display
|
|
self.debug = False
|
|
self.intensity = 16
|
|
self.icons = []
|
|
self.icon_id = 0
|
|
self.states = []
|
|
self.on_screen_icons = []
|
|
if not config:
|
|
return
|
|
if 'debug' in config:
|
|
self.debug = config['debug']
|
|
if 'intensity' in config:
|
|
self.intensity = int(round(config['intensity']*255))
|
|
if 'icons' in config:
|
|
for filename in config['icons']:
|
|
self.add_icon(filename)
|
|
self.set_intensity(self.intensity)
|
|
|
|
def reset(self):
|
|
while self.load_icon():
|
|
pass
|
|
|
|
def set_intensity(self, value=None):
|
|
if value is not None:
|
|
self.intensity -= 1
|
|
if not self.intensity:
|
|
self.intensity = 16
|
|
for i in self.icons:
|
|
i.set_intensity(self.intensity)
|
|
return self.intensity
|
|
|
|
def input(self, button_state):
|
|
"""
|
|
Handle button input
|
|
"""
|
|
return 0 # signal that we did not handle the input
|
|
|
|
def render(self, frame, dropped_frames, fps):
|
|
t0 = time.time()
|
|
display = self.display
|
|
intensity = self.intensity
|
|
unload_queue = []
|
|
for state in self.on_screen_icons:
|
|
if frame < state['next_frame_at']:
|
|
continue
|
|
|
|
state['remaining_frames'] -= 1
|
|
if state['remaining_frames'] == 0:
|
|
# Queue icon for removal from screen
|
|
unload_queue.append(state['i'])
|
|
|
|
n = state['num_frames']
|
|
index = n - (state['remaining_frames'] % n) - 1
|
|
x_pos = state['x_pos']
|
|
y_pos = state['y_pos']
|
|
icon = self.icons[state['i']]
|
|
# Do not repaint until some specified time in the future
|
|
state['next_frame_at'] = frame + int(fps * icon.frame_length() / 1000)
|
|
# Render icon
|
|
icon.blit(self.display, x_pos, y_pos)
|
|
|
|
t2 = time.time()
|
|
t1 = t2 - t0
|
|
display.render()
|
|
t3 = time.time()
|
|
t2 = t3 - t2
|
|
if self.debug:
|
|
print('AnimationScene: Spent {}ms plotting icons, {}ms updating LedMatrix+HAL, {}ms total'.format(round(t1*1000.0), round(t2*1000.0), round((t3-t0)*1000.0)))
|
|
|
|
for i in unload_queue:
|
|
self.unload_icon(i)
|
|
|
|
if not self.on_screen_icons:
|
|
return False # Nothing more to display
|
|
|
|
return True # We still have icons left to render
|
|
|
|
def add_icon(self, filename):
|
|
"""
|
|
See animations/README.md for details
|
|
"""
|
|
icon = Icon(filename)
|
|
self.icons.append(icon)
|
|
|
|
def load_icon(self):
|
|
"""
|
|
Load icon into first available slot
|
|
"""
|
|
cols = bytearray(b' ' * 32)
|
|
icon_width = 8
|
|
padding = 1 if self.display.columns == 32 else 0
|
|
for state in self.on_screen_icons:
|
|
icon_x = state['x_pos'] + (state['y_pos']<<1)
|
|
cols[icon_x:icon_x+icon_width] = ('x'*icon_width).encode()
|
|
|
|
x = 0
|
|
space = ord(' ')
|
|
need = icon_width+padding
|
|
for i in range(32):
|
|
if cols[i] != space:
|
|
x = i+1
|
|
elif i+1 == x+need:
|
|
break
|
|
if i+1 != x+need:
|
|
# no available space
|
|
return False
|
|
if not x:
|
|
# center for 32x8 displays
|
|
x += 3 if self.display.columns == 32 else 0
|
|
else:
|
|
# left-pad next icon
|
|
x += padding
|
|
|
|
icon = self.icons[self.icon_id]
|
|
num_frames = icon.frame_count()
|
|
state = {
|
|
'i': self.icon_id, # for unloading the icon
|
|
'x_pos': x if self.display.columns == 32 else x & 0xf,
|
|
'y_pos': 0 if self.display.columns == 32 else (x >> 4) << 3,
|
|
'num_frames': num_frames, # cached for convenience
|
|
'remaining_frames': num_frames, # keep track of the currently rendered frame
|
|
'next_frame_at': 0 # for handling delays
|
|
}
|
|
|
|
# Ensure a minimum display time
|
|
t_icon = icon.length_total()
|
|
for i in range(1,6):
|
|
if t_icon*i >= 4000:
|
|
break
|
|
state['remaining_frames'] += num_frames
|
|
|
|
self.on_screen_icons.append(state)
|
|
if self.debug:
|
|
print('Animation: loaded icon {} at ({}, {})'.format(self.icon_id, state['x_pos'], state['y_pos']))
|
|
self.icon_id = (self.icon_id + 1) % len(self.icons)
|
|
return True
|
|
|
|
def unload_icon(self, i):
|
|
display = self.display
|
|
for state in self.on_screen_icons:
|
|
if state['i'] == i:
|
|
icon = self.icons[i]
|
|
height = icon.rows
|
|
width = icon.cols
|
|
x_pos = state['x_pos']
|
|
y_pos = state['y_pos']
|
|
for y in range(height):
|
|
for x in range(width):
|
|
display.put_pixel(x_pos+x, y_pos+y, 0, 0, 0)
|
|
self.on_screen_icons.remove(state)
|
|
return
|
|
|