141 lines
4.2 KiB
Python
Executable File
141 lines
4.2 KiB
Python
Executable File
#!/usr/bin/env python
|
|
#
|
|
import time
|
|
import json
|
|
|
|
class AnimationScene:
|
|
"""Render animations from https://developer.lametric.com"""
|
|
|
|
def __init__(self, display, config):
|
|
self.name = 'Animation'
|
|
self.display = display
|
|
self.objs = []
|
|
self.obj_i = 0
|
|
self.states = []
|
|
self.on_screen_objs = []
|
|
if config and 'files' in config:
|
|
for filename in config['files']:
|
|
self.add_obj(filename)
|
|
|
|
def add_obj(self, filename):
|
|
# This method expects an animation as downloaded from LaMetric's developer site
|
|
#
|
|
# Example:
|
|
# curl -sA '' https://developer.lametric.com/api/v1/dev/preloadicons?icon_id=4007 > blah.json
|
|
#
|
|
# To get an index of available animations, try:
|
|
# curl -sA '' 'https://developer.lametric.com/api/v1/dev/preloadicons?page=1&category=popular&search=&count=5000' | tee popular-24k-p1.json
|
|
#
|
|
with open(filename) as f:
|
|
data = f.read()
|
|
obj = json.loads(data)
|
|
obj = json.loads(obj['body'])
|
|
self.objs.append(obj)
|
|
|
|
def reset(self):
|
|
print('Animation: reset called, loading animation objects')
|
|
while self.load_obj():
|
|
pass
|
|
|
|
def input(self, button_id, button_state):
|
|
"""
|
|
Handle button input
|
|
"""
|
|
print('Animation: button {} pressed: {}'.format(button_id, button_state))
|
|
return False # signal that we did not handle the input
|
|
|
|
def load_obj(self):
|
|
"""
|
|
Load object into first available slot
|
|
"""
|
|
cols = bytearray(' ' * 32)
|
|
obj_width = 8
|
|
padding = 1
|
|
for state in self.on_screen_objs:
|
|
obj_x = state['x_pos']
|
|
cols[obj_x:obj_x+obj_width] = ('x'*obj_width).encode()
|
|
x = cols.find(' ' * (obj_width + padding))
|
|
if x < 0:
|
|
# no available space
|
|
print('Animation: not enough columns to add another object')
|
|
return False
|
|
if not x:
|
|
# center
|
|
x += 3
|
|
else:
|
|
# left-pad next animation
|
|
x += padding
|
|
|
|
obj = self.objs[self.obj_i]
|
|
num_frames = len(obj['icons'])
|
|
state = {
|
|
'i': self.obj_i, # for unloading the object
|
|
'x_pos': x,
|
|
'frames': obj['icons'],
|
|
'frame_delay_ms': obj['delays'],
|
|
'num_frames': num_frames, # cached for convenience
|
|
'remaining_frames': 2*num_frames, # keep track of the currently rendered frame
|
|
'next_frame_at': 0 # for handling delays
|
|
}
|
|
self.on_screen_objs.append(state)
|
|
print('Animation: loaded object {} at column {}'.format(self.obj_i, x))
|
|
self.obj_i = (self.obj_i + 1) % len(self.objs)
|
|
return True
|
|
|
|
def unload_obj(self, i):
|
|
display = self.display
|
|
for state in self.on_screen_objs:
|
|
if state['i'] == i:
|
|
height = len(state['frames'][0])
|
|
width = len(state['frames'][0][0])
|
|
x_pos = state['x_pos']
|
|
for y in range(height):
|
|
for x in range(width):
|
|
display.put_pixel(x_pos+x, y, 0, 0, 0)
|
|
self.on_screen_objs.remove(state)
|
|
print('Animation: unloaded object {} from column {}'.format(i, x_pos))
|
|
return
|
|
|
|
def render(self, frame, dropped_frames, fps):
|
|
t0 = time.time()
|
|
display = self.display
|
|
unload_queue = []
|
|
for state in self.on_screen_objs:
|
|
if frame < state['next_frame_at']:
|
|
continue
|
|
|
|
state['remaining_frames'] -= 1
|
|
if state['remaining_frames'] == 0:
|
|
# Queue object for removal
|
|
unload_queue.append(state['i'])
|
|
|
|
n = state['num_frames']
|
|
index = n - (state['remaining_frames'] % n) - 1
|
|
data = state['frames'][index]
|
|
x_pos = state['x_pos']
|
|
for y in range(len(data)):
|
|
row = data[y]
|
|
for x in range(len(row)):
|
|
r = round(row[x][0] * 255)
|
|
g = round(row[x][1] * 255)
|
|
b = round(row[x][2] * 255)
|
|
display.put_pixel(x_pos+x, y, r, g, b)
|
|
# Do not repaint until some spe
|
|
state['next_frame_at'] = frame + int(fps * state['frame_delay_ms'][index] / 1000)
|
|
print('AnimationScene: obj {}: queueing repaint at frame {}+{}=={}, fps {}, delay {}'.format(state['i'], frame, int(fps * state['frame_delay_ms'][index] / 1000), state['next_frame_at'], fps, state['frame_delay_ms'][index]))
|
|
t1 = time.time() - t0
|
|
|
|
t2 = time.time()
|
|
display.render()
|
|
t3 = time.time() - t2
|
|
print('AnimationScene: Spent {}ms plotting objects, {}ms updating LedMatrix+HAL, {}ms total'.format(round(1000*t1), round(1000*t2), round(1000*(time.time()-t0))))
|
|
|
|
for i in unload_queue:
|
|
self.unload_obj(i)
|
|
|
|
if not self.on_screen_objs:
|
|
# Nothing more to display
|
|
return False
|
|
# We still have objects left to render
|
|
return True
|