#!/usr/bin/env python3 import sys import random import argparse import collections import blup.frame import blup.output import blup.animation import blup.writebml import colorsys import math WIDTH = 22 HEIGHT = 16 DEPTH = 256 _ALLSHAPES = [ [[0, 1, 0, 1, 0], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [0, 1, 1, 1, 0], [0, 0, 1, 0, 0]], [[1, 0, 0, 0, 1], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [1, 0, 0, 0, 1], [0, 1, 1, 1, 0]], [[1, 0, 1, 0, 1], [0, 1, 1, 1, 0], [0, 1, 1, 1, 0], [0, 1, 0, 1, 0], [1, 1, 0, 1, 1]], ] class Point(collections.namedtuple('Point', ['x', 'y'])): def __add__(self, other): return Point(self.x + other.x, self.y + other.y) def __sub__(self, other): return Point(self.x - other.x, self.y - other.y) ALLSHAPES = [] for s in _ALLSHAPES: ALLSHAPES.append(set()) y = 0 for l in s: x = 0 for i in l: if i == 1: ALLSHAPES[-1].add(Point(x, y)) x += 1 y += 1 def convert_color(c): return list(map(lambda x: int(round(x*(DEPTH-1))), c)) def get_random_color(): return colorsys.hsv_to_rgb(random.random(), 1, 1) class Shape: def __init__(self, shape, pos, color): self.shape, self.pos, self.color = shape, pos, color self.period = 2 self.fadetime = 0.5 + random.random() / 5 self.t = random.random() * self.period self.t = 0 self.oldv = 1 self.v = 1 self.newshape = None def update(self, t, dt): self.t += dt if self.t > self.period: t = self.t - self.period if t >= self.fadetime: self.v = 1 self.t = random.random() * self.period else: self.v = math.cos(t * (math.pi / self.fadetime)) ** 2 if (self.newshape is not None \ and (self.oldv < self.v or self.v == 0)): self.shape = self.newshape self.color = self.newcolor self.newshape = None self.oldv = self.v def change_shape_and_color(self, newshape, newcolor): self.newshape = newshape self.newcolor = newcolor self.t = self.period def draw(self, frame, origin=Point(0, 0)): h, s, v = colorsys.rgb_to_hsv(*self.color) for p in self.shape: try: pos = self.pos + p - origin c = colorsys.hsv_to_rgb(h, s, v * self.v) frame.setPixel(pos.x, pos.y, convert_color(c)) except ValueError: pass class World: def __init__(self, size, allshapes, gap=2): self.size, self.allshapes, self.gap = size, allshapes, gap self.shapes = {} self.pos = Point(0, 0) self.cycletime = 60 self.curshape = random.choice(allshapes) self.curcolor = get_random_color() self.shapesize = Point(0, 0) for s in allshapes: assert min(map(lambda p: p.x, s)) >= 0 assert min(map(lambda p: p.y, s)) >= 0 shapesize = Point( max(map(lambda p: p.x, s)) + 1, max(map(lambda p: p.y, s)) + 1, ) if shapesize.x > self.shapesize.x: self.shapesize = Point(shapesize.x, self.shapesize.y) if shapesize.y > self.shapesize.y: self.shapesize = Point(self.shapesize.x, shapesize.y) print (self.shapesize) @property def shape_space(self): ret = set() for x in range(self.size.x): for y in range(self.size.y): p = self.pos + Point(x, y) if (p.x % (self.shapesize.x + self.gap) >= self.gap and p.y % (self.shapesize.y + self.gap) >= self.gap): ret.add(p) return ret def get_shape_coverage(self, shape): return { shape.pos + Point(x, y) for x in range(self.shapesize.x) \ for y in range(self.shapesize.y) } def update(self, t, dt): self.pos = Point( int(math.sin((t * math.pi/self.cycletime)*4) * self.size.x), int(math.cos((t * math.pi/self.cycletime)*5) * self.size.y) ) space = self.shape_space for s in self.shapes.values(): space -= self.get_shape_coverage(s) while len(space) > 0: # and len(self.shapes) < 1: print('...') p = space.pop() newpos = Point( (p.x // (self.shapesize.x + self.gap)) \ * (self.shapesize.x + self.gap) + self.gap, (p.y // (self.shapesize.y + self.gap)) \ * (self.shapesize.y + self.gap) + self.gap ) print(p, newpos) # TODO improve shape and color selection newshape = Shape(self.curshape, newpos, self.curcolor) assert newpos in self.get_shape_coverage(newshape) print('covered', self.get_shape_coverage(newshape)) print('space before', len(space)) space -= self.get_shape_coverage(newshape) print('space after ', len(space)) assert newpos not in self.shapes self.shapes[newpos] = newshape print('new', len(self.shapes)) #print(space) newshape = None newcolor = None if round(t*1000) % 3000 == 0: newshape = random.choice(self.allshapes) self.curshape = newshape newcolor = get_random_color() self.curcolor = newcolor for s in self.shapes.values(): if newshape is not None: s.change_shape_and_color(newshape, newcolor) s.update(t, dt) def draw(self, frame): for s in self.shapes.values(): s.draw(frame, self.pos) if __name__ == '__main__': parser = argparse.ArgumentParser(description='Generate animations') parser.add_argument('-d', '--delay', type=int, default=50) parser.add_argument('-t', '--time', type=int, default=15) parser.add_argument('output_file') args = parser.parse_args() dim = blup.frame.FrameDimension(WIDTH, HEIGHT, DEPTH, 3) anim = blup.animation.Animation(dim) anim.tags['description'] = ' '.join(sys.argv) w = World(Point(WIDTH, HEIGHT), ALLSHAPES) t = 0 dt = args.delay / 1000 while t < args.time: t += dt frame = blup.animation.AnimationFrame(dim, args.delay) w.update(t, dt) w.draw(frame) anim.addFrame(frame) blup.writebml.writeBml(anim, args.output_file)