diff --git a/nyan/__main__.py b/nyan/__main__.py index b91b745..b813a22 100644 --- a/nyan/__main__.py +++ b/nyan/__main__.py @@ -1,94 +1,24 @@ #!/usr/bin/env python3 -from collections import namedtuple import math import os -import os.path as path -import random -import sys import time -from PIL import Image # Requires the Pillow library +from nyancat import Nyancat _geometry = os.getenv('LEDCAT_GEOMETRY', '150x16').split('x') DISP_WIDTH = int(_geometry[0]) DISP_HEIGHT = int(_geometry[1]) +class NyancatWave(Nyancat): + def __init__(self, w, h): + super().__init__(w, h) -tail_colors = [ - (0xff, 0x00, 0x00), - (0xff, 0x99, 0x00), - (0xff, 0xff, 0x00), - (0x00, 0xff, 0x00), - (0x00, 0x99, 0xff), - (0x66, 0x33, 0xff), -] + def plot_tail(self, width): + t = time.time() + for x in range(width): + yield (DISP_HEIGHT // 2) + int(math.sin(x / 6 + t * math.pi) * 4 * math.sin(t * 8)) -def read_image(filename): - img = Image.open(filename) - return img -anim_cat = [] -for i in range(0, 6): - frame = read_image('%s/cat/%d.png' % (path.dirname(__file__), i)) - assert (32, 16) == frame.size - anim_cat.append(frame) -anim_sparkle = [] -for i in range(0, 5): - frame = read_image('%s/sparkle/%d.png' % (path.dirname(__file__), i)) - anim_sparkle.append(frame) - -class Sparkle(object): - def __init__(self): - half_h = anim_sparkle[0].size[1] // 2 - self.x = random.randint(0, DISP_WIDTH) - self.y = random.randint(half_h, DISP_HEIGHT - half_h) - self.frame_index = 0 - -sparkles = [] +cat = NyancatWave(DISP_WIDTH, DISP_HEIGHT) while True: - for anim_frame in anim_cat: - if len(sparkles) < 32: - sparkles.append(Sparkle()) - - frame = bytearray(DISP_WIDTH * DISP_HEIGHT * 3) - - # Render the tail - for x in range(DISP_WIDTH): - for y in range(DISP_HEIGHT): - i = y * DISP_WIDTH + x - t = time.time() - col_y = y - (DISP_HEIGHT // 2 - len(tail_colors) // 2) + int(math.sin(x / 6 + t * math.pi) * 4 * math.sin(t * 8)) - if x < (DISP_WIDTH - 10) and 0 <= col_y < len(tail_colors): - color = tail_colors[col_y] - else: - color = (0x0f, 0x4d, 0x8f) - frame[i*3:i*3+3] = color - - # Copy animated frame - for anim_x in range(anim_frame.size[0]): - for anim_y in range(anim_frame.size[1]): - pix = anim_frame.getpixel((anim_x, anim_y)) - if pix[3] != 0: # Test for alpha - x = (DISP_WIDTH - anim_frame.size[0]) + anim_x - y = (DISP_HEIGHT // 2 - anim_frame.size[1] // 2) + anim_y - i = ((y * DISP_WIDTH) + x) * 3 - frame[i:i+3] = pix[:3] - - # Render and update the sparkles - for sp in sparkles: - for x in range(anim_sparkle[0].size[0]): - for y in range(anim_sparkle[0].size[1]): - pix_x = sp.x + x - anim_sparkle[0].size[0] // 2 - pix_y = sp.y + y - anim_sparkle[0].size[1] // 2 - if 0 <= pix_x < DISP_WIDTH and 0 <= pix_y < DISP_HEIGHT: - i = pix_y * DISP_WIDTH + pix_x - pix = anim_sparkle[sp.frame_index].getpixel((x, y)) - if pix[3] != 0: - frame[i*3:i*3+3] = pix[:3] - sp.x -= 3 - sp.frame_index += 1 - # Remove expired sparkles - sparkles = list(filter(lambda sp: sp.frame_index < len(anim_sparkle), sparkles)) - - sys.stdout.buffer.write(frame) - time.sleep(1 / 30) + cat.render() diff --git a/nyan/nyancat.py b/nyan/nyancat.py new file mode 100644 index 0000000..56470d6 --- /dev/null +++ b/nyan/nyancat.py @@ -0,0 +1,88 @@ +import os.path as path +import random +import sys +import time +from PIL import Image # Requires the Pillow library + +background_color = (0x0f, 0x4d, 0x8f) +tail_colors = [ + (0xff, 0x00, 0x00), + (0xff, 0x99, 0x00), + (0xff, 0xff, 0x00), + (0x00, 0xff, 0x00), + (0x00, 0x99, 0xff), + (0x66, 0x33, 0xff), +] + +anim_cat = [] +for i in range(0, 6): + frame = Image.open('%s/cat/%d.png' % (path.dirname(__file__), i)) + assert (32, 16) == frame.size + anim_cat.append(frame) +anim_sparkle = [] +for i in range(0, 5): + frame = Image.open('%s/sparkle/%d.png' % (path.dirname(__file__), i)) + anim_sparkle.append(frame) + + +class Sparkle(object): + def __init__(self, w, h): + half_h = anim_sparkle[0].size[1] // 2 + self.x = random.randint(0, w) + self.y = random.randint(half_h, h - half_h) + self.frame_index = 0 + +class Nyancat(object): + def __init__(self, width, height): + self.width = width + self.height = height + self.sparkles = [] + + def render(self): + for anim_frame in anim_cat: + if len(self.sparkles) < 32: + self.sparkles.append(Sparkle(self.width, self.height)) + + frame = bytearray(self.width * self.height * 3) + # Render the background + for i in range(self.width * self.height): + frame[i*3:i*3+3] = background_color + + # Render the tail + for (x, tail_y) in enumerate(self.plot_tail(self.width - 10)): + for (y, color) in enumerate(tail_colors, tail_y - len(tail_colors) // 2): + if 0 <= y < self.height: + i = y * self.width + x + frame[i*3:i*3+3] = color + + # Copy animated frame + for anim_x in range(anim_frame.size[0]): + for anim_y in range(anim_frame.size[1]): + pix = anim_frame.getpixel((anim_x, anim_y)) + if pix[3] != 0: # Test for alpha + x = (self.width - anim_frame.size[0]) + anim_x + y = (self.height // 2 - anim_frame.size[1] // 2) + anim_y + i = ((y * self.width) + x) * 3 + frame[i:i+3] = pix[:3] + + # Render and update the sparkles + for sp in self.sparkles: + for x in range(anim_sparkle[0].size[0]): + for y in range(anim_sparkle[0].size[1]): + pix_x = sp.x + x - anim_sparkle[0].size[0] // 2 + pix_y = sp.y + y - anim_sparkle[0].size[1] // 2 + if 0 <= pix_x < self.width and 0 <= pix_y < self.height: + i = pix_y * self.width + pix_x + pix = anim_sparkle[sp.frame_index].getpixel((x, y)) + if pix[3] != 0: + frame[i*3:i*3+3] = pix[:3] + sp.x -= 3 + sp.frame_index += 1 + # Remove expired sparkles + self.sparkles = list(filter(lambda sp: sp.frame_index < len(anim_sparkle), self.sparkles)) + + sys.stdout.buffer.write(frame) + time.sleep(1 / 30) + + def plot_tail(self, width): + pass # Virtual