diff options
author | jaseg <jaseg@jaseg.net> | 2014-01-03 00:10:20 +0100 |
---|---|---|
committer | jaseg <jaseg@jaseg.net> | 2014-01-03 00:10:20 +0100 |
commit | 916aced1bef07001ef4eee2a1cde6cd6e33b4bc3 (patch) | |
tree | a8af06f0dcc65645801a2e20db64ca877b288f98 /host | |
parent | 34def4f660108632a9526c9ad3195fdb883d206d (diff) | |
download | matelight-916aced1bef07001ef4eee2a1cde6cd6e33b4bc3.tar.gz matelight-916aced1bef07001ef4eee2a1cde6cd6e33b4bc3.tar.bz2 matelight-916aced1bef07001ef4eee2a1cde6cd6e33b4bc3.zip |
Now with even more abstract art.
Diffstat (limited to 'host')
-rw-r--r-- | host/matelight/Makefile | 4 | ||||
-rw-r--r-- | host/matelight/color.c | 291 | ||||
-rw-r--r-- | host/matelight/color.h | 21 | ||||
-rw-r--r-- | host/matelight/font.c | 5 | ||||
-rw-r--r-- | host/matelight/font.h | 5 | ||||
-rw-r--r-- | host/matelight/font.py | 5 | ||||
-rwxr-xr-x | host/matelight/genpal.py | 35 | ||||
-rw-r--r-- | host/matelight/host.py | 19 | ||||
-rwxr-xr-x | host/matelight/listeners.py | 98 | ||||
-rw-r--r-- | host/matelight/main.c | 256 | ||||
-rwxr-xr-x | host/matelight/nyancat-test.py | 29 | ||||
-rw-r--r-- | host/matelight/queuemgr.py | 52 | ||||
-rw-r--r-- | host/matelight/renderers.py | 154 | ||||
-rwxr-xr-x | host/matelight/scroll-test.py | 14 |
14 files changed, 600 insertions, 388 deletions
diff --git a/host/matelight/Makefile b/host/matelight/Makefile index 2a7e971..1f90145 100644 --- a/host/matelight/Makefile +++ b/host/matelight/Makefile @@ -1,6 +1,6 @@ -all: main.c font.c font.h - gcc -std=gnu11 -Wall -lm -o matelight -g -O0 main.c font.c +all: main.c font.c font.h color.c color.h + gcc -std=gnu11 -Wall -lm -o matelight -g -O0 main.c font.c color.c clean: rm matelight diff --git a/host/matelight/color.c b/host/matelight/color.c new file mode 100644 index 0000000..938bdf1 --- /dev/null +++ b/host/matelight/color.c @@ -0,0 +1,291 @@ + +#include "color.h" +#include <stdlib.h> +#include <stdio.h> + +int xterm_color_index(color_t c){ + int candidate = 0; + int best_distance = 0x7fffffff; + for(int i=0; i<256; i++){ + color_t k = colortable[i]; + int tmp = abs(c.r - k.r); + tmp *= tmp; + int distance = tmp; + if(distance > best_distance) + continue; + tmp = abs(c.g - k.g); + tmp *= tmp; + distance += tmp; + if(distance > best_distance) + continue; + tmp = abs(c.b - k.b); + tmp *= tmp; + distance += tmp; + if(distance > best_distance) + continue; + best_distance = distance; + candidate = i; + } + return candidate; +} + +color_t colortable[256] = { + {0x00, 0x00, 0x00}, + {0xa8, 0x00, 0x00}, + {0x00, 0xa8, 0x00}, + {0xa8, 0x54, 0x00}, + {0x00, 0x00, 0xa8}, + {0xa8, 0x00, 0xa8}, + {0x00, 0xa8, 0xa8}, + {0xa8, 0xa8, 0xa8}, + {0x54, 0x54, 0x54}, + {0xfc, 0x54, 0x54}, + {0x54, 0xfc, 0x54}, + {0xfc, 0xfc, 0x54}, + {0x54, 0x54, 0xfc}, + {0xfc, 0x54, 0xfc}, + {0x54, 0xfc, 0xfc}, + {0xfc, 0xfc, 0xfc}, + {0x00, 0x00, 0x00}, + {0x00, 0x00, 0x5f}, + {0x00, 0x00, 0x87}, + {0x00, 0x00, 0xaf}, + {0x00, 0x00, 0xd7}, + {0x00, 0x00, 0xff}, + {0x00, 0x5f, 0x00}, + {0x00, 0x5f, 0x5f}, + {0x00, 0x5f, 0x87}, + {0x00, 0x5f, 0xaf}, + {0x00, 0x5f, 0xd7}, + {0x00, 0x5f, 0xff}, + {0x00, 0x87, 0x00}, + {0x00, 0x87, 0x5f}, + {0x00, 0x87, 0x87}, + {0x00, 0x87, 0xaf}, + {0x00, 0x87, 0xd7}, + {0x00, 0x87, 0xff}, + {0x00, 0xaf, 0x00}, + {0x00, 0xaf, 0x5f}, + {0x00, 0xaf, 0x87}, + {0x00, 0xaf, 0xaf}, + {0x00, 0xaf, 0xd7}, + {0x00, 0xaf, 0xff}, + {0x00, 0xd7, 0x00}, + {0x00, 0xd7, 0x5f}, + {0x00, 0xd7, 0x87}, + {0x00, 0xd7, 0xaf}, + {0x00, 0xd7, 0xd7}, + {0x00, 0xd7, 0xff}, + {0x00, 0xff, 0x00}, + {0x00, 0xff, 0x5f}, + {0x00, 0xff, 0x87}, + {0x00, 0xff, 0xaf}, + {0x00, 0xff, 0xd7}, + {0x00, 0xff, 0xff}, + {0x5f, 0x00, 0x00}, + {0x5f, 0x00, 0x5f}, + {0x5f, 0x00, 0x87}, + {0x5f, 0x00, 0xaf}, + {0x5f, 0x00, 0xd7}, + {0x5f, 0x00, 0xff}, + {0x5f, 0x5f, 0x00}, + {0x5f, 0x5f, 0x5f}, + {0x5f, 0x5f, 0x87}, + {0x5f, 0x5f, 0xaf}, + {0x5f, 0x5f, 0xd7}, + {0x5f, 0x5f, 0xff}, + {0x5f, 0x87, 0x00}, + {0x5f, 0x87, 0x5f}, + {0x5f, 0x87, 0x87}, + {0x5f, 0x87, 0xaf}, + {0x5f, 0x87, 0xd7}, + {0x5f, 0x87, 0xff}, + {0x5f, 0xaf, 0x00}, + {0x5f, 0xaf, 0x5f}, + {0x5f, 0xaf, 0x87}, + {0x5f, 0xaf, 0xaf}, + {0x5f, 0xaf, 0xd7}, + {0x5f, 0xaf, 0xff}, + {0x5f, 0xd7, 0x00}, + {0x5f, 0xd7, 0x5f}, + {0x5f, 0xd7, 0x87}, + {0x5f, 0xd7, 0xaf}, + {0x5f, 0xd7, 0xd7}, + {0x5f, 0xd7, 0xff}, + {0x5f, 0xff, 0x00}, + {0x5f, 0xff, 0x5f}, + {0x5f, 0xff, 0x87}, + {0x5f, 0xff, 0xaf}, + {0x5f, 0xff, 0xd7}, + {0x5f, 0xff, 0xff}, + {0x87, 0x00, 0x00}, + {0x87, 0x00, 0x5f}, + {0x87, 0x00, 0x87}, + {0x87, 0x00, 0xaf}, + {0x87, 0x00, 0xd7}, + {0x87, 0x00, 0xff}, + {0x87, 0x5f, 0x00}, + {0x87, 0x5f, 0x5f}, + {0x87, 0x5f, 0x87}, + {0x87, 0x5f, 0xaf}, + {0x87, 0x5f, 0xd7}, + {0x87, 0x5f, 0xff}, + {0x87, 0x87, 0x00}, + {0x87, 0x87, 0x5f}, + {0x87, 0x87, 0x87}, + {0x87, 0x87, 0xaf}, + {0x87, 0x87, 0xd7}, + {0x87, 0x87, 0xff}, + {0x87, 0xaf, 0x00}, + {0x87, 0xaf, 0x5f}, + {0x87, 0xaf, 0x87}, + {0x87, 0xaf, 0xaf}, + {0x87, 0xaf, 0xd7}, + {0x87, 0xaf, 0xff}, + {0x87, 0xd7, 0x00}, + {0x87, 0xd7, 0x5f}, + {0x87, 0xd7, 0x87}, + {0x87, 0xd7, 0xaf}, + {0x87, 0xd7, 0xd7}, + {0x87, 0xd7, 0xff}, + {0x87, 0xff, 0x00}, + {0x87, 0xff, 0x5f}, + {0x87, 0xff, 0x87}, + {0x87, 0xff, 0xaf}, + {0x87, 0xff, 0xd7}, + {0x87, 0xff, 0xff}, + {0xaf, 0x00, 0x00}, + {0xaf, 0x00, 0x5f}, + {0xaf, 0x00, 0x87}, + {0xaf, 0x00, 0xaf}, + {0xaf, 0x00, 0xd7}, + {0xaf, 0x00, 0xff}, + {0xaf, 0x5f, 0x00}, + {0xaf, 0x5f, 0x5f}, + {0xaf, 0x5f, 0x87}, + {0xaf, 0x5f, 0xaf}, + {0xaf, 0x5f, 0xd7}, + {0xaf, 0x5f, 0xff}, + {0xaf, 0x87, 0x00}, + {0xaf, 0x87, 0x5f}, + {0xaf, 0x87, 0x87}, + {0xaf, 0x87, 0xaf}, + {0xaf, 0x87, 0xd7}, + {0xaf, 0x87, 0xff}, + {0xaf, 0xaf, 0x00}, + {0xaf, 0xaf, 0x5f}, + {0xaf, 0xaf, 0x87}, + {0xaf, 0xaf, 0xaf}, + {0xaf, 0xaf, 0xd7}, + {0xaf, 0xaf, 0xff}, + {0xaf, 0xd7, 0x00}, + {0xaf, 0xd7, 0x5f}, + {0xaf, 0xd7, 0x87}, + {0xaf, 0xd7, 0xaf}, + {0xaf, 0xd7, 0xd7}, + {0xaf, 0xd7, 0xff}, + {0xaf, 0xff, 0x00}, + {0xaf, 0xff, 0x5f}, + {0xaf, 0xff, 0x87}, + {0xaf, 0xff, 0xaf}, + {0xaf, 0xff, 0xd7}, + {0xaf, 0xff, 0xff}, + {0xd7, 0x00, 0x00}, + {0xd7, 0x00, 0x5f}, + {0xd7, 0x00, 0x87}, + {0xd7, 0x00, 0xaf}, + {0xd7, 0x00, 0xd7}, + {0xd7, 0x00, 0xff}, + {0xd7, 0x5f, 0x00}, + {0xd7, 0x5f, 0x5f}, + {0xd7, 0x5f, 0x87}, + {0xd7, 0x5f, 0xaf}, + {0xd7, 0x5f, 0xd7}, + {0xd7, 0x5f, 0xff}, + {0xd7, 0x87, 0x00}, + {0xd7, 0x87, 0x5f}, + {0xd7, 0x87, 0x87}, + {0xd7, 0x87, 0xaf}, + {0xd7, 0x87, 0xd7}, + {0xd7, 0x87, 0xff}, + {0xd7, 0xaf, 0x00}, + {0xd7, 0xaf, 0x5f}, + {0xd7, 0xaf, 0x87}, + {0xd7, 0xaf, 0xaf}, + {0xd7, 0xaf, 0xd7}, + {0xd7, 0xaf, 0xff}, + {0xd7, 0xd7, 0x00}, + {0xd7, 0xd7, 0x5f}, + {0xd7, 0xd7, 0x87}, + {0xd7, 0xd7, 0xaf}, + {0xd7, 0xd7, 0xd7}, + {0xd7, 0xd7, 0xff}, + {0xd7, 0xff, 0x00}, + {0xd7, 0xff, 0x5f}, + {0xd7, 0xff, 0x87}, + {0xd7, 0xff, 0xaf}, + {0xd7, 0xff, 0xd7}, + {0xd7, 0xff, 0xff}, + {0xff, 0x00, 0x00}, + {0xff, 0x00, 0x5f}, + {0xff, 0x00, 0x87}, + {0xff, 0x00, 0xaf}, + {0xff, 0x00, 0xd7}, + {0xff, 0x00, 0xff}, + {0xff, 0x5f, 0x00}, + {0xff, 0x5f, 0x5f}, + {0xff, 0x5f, 0x87}, + {0xff, 0x5f, 0xaf}, + {0xff, 0x5f, 0xd7}, + {0xff, 0x5f, 0xff}, + {0xff, 0x87, 0x00}, + {0xff, 0x87, 0x5f}, + {0xff, 0x87, 0x87}, + {0xff, 0x87, 0xaf}, + {0xff, 0x87, 0xd7}, + {0xff, 0x87, 0xff}, + {0xff, 0xaf, 0x00}, + {0xff, 0xaf, 0x5f}, + {0xff, 0xaf, 0x87}, + {0xff, 0xaf, 0xaf}, + {0xff, 0xaf, 0xd7}, + {0xff, 0xaf, 0xff}, + {0xff, 0xd7, 0x00}, + {0xff, 0xd7, 0x5f}, + {0xff, 0xd7, 0x87}, + {0xff, 0xd7, 0xaf}, + {0xff, 0xd7, 0xd7}, + {0xff, 0xd7, 0xff}, + {0xff, 0xff, 0x00}, + {0xff, 0xff, 0x5f}, + {0xff, 0xff, 0x87}, + {0xff, 0xff, 0xaf}, + {0xff, 0xff, 0xd7}, + {0xff, 0xff, 0xff}, + {0x00, 0x00, 0x00}, + {0x12, 0x12, 0x12}, + {0x1c, 0x1c, 0x1c}, + {0x26, 0x26, 0x26}, + {0x30, 0x30, 0x30}, + {0x3a, 0x3a, 0x3a}, + {0x44, 0x44, 0x44}, + {0x4e, 0x4e, 0x4e}, + {0x58, 0x58, 0x58}, + {0x62, 0x62, 0x62}, + {0x6c, 0x6c, 0x6c}, + {0x76, 0x76, 0x76}, + {0x80, 0x80, 0x80}, + {0x8a, 0x8a, 0x8a}, + {0x94, 0x94, 0x94}, + {0x9e, 0x9e, 0x9e}, + {0xa8, 0xa8, 0xa8}, + {0xb2, 0xb2, 0xb2}, + {0xbc, 0xbc, 0xbc}, + {0xc6, 0xc6, 0xc6}, + {0xd0, 0xd0, 0xd0}, + {0xda, 0xda, 0xda}, + {0xe4, 0xe4, 0xe4}, + {0xee, 0xee, 0xee} +}; + + diff --git a/host/matelight/color.h b/host/matelight/color.h new file mode 100644 index 0000000..48f325b --- /dev/null +++ b/host/matelight/color.h @@ -0,0 +1,21 @@ +#ifndef __COLOR_H__ +#define __COLOR_H__ + +#include <stdint.h> + +typedef struct { + uint8_t r; + uint8_t g; + uint8_t b; +} color_t; + +int xterm_color_index(color_t c); + +// gray +#define DEFAULT_FG_COLOR 7 +// black +#define DEFAULT_BG_COLOR 0 + +extern color_t colortable[256]; + +#endif//__COLOR_H__ diff --git a/host/matelight/font.c b/host/matelight/font.c index 53d27b9..f22f05b 100644 --- a/host/matelight/font.c +++ b/host/matelight/font.c @@ -5,7 +5,7 @@ #include <stdlib.h> #include <string.h> -void render_glyph(glyph_t *g, uint8_t *buf, unsigned int bufwidth, unsigned int offx, unsigned int offy){ +void render_glyph(glyph_t *g, uint8_t *buf, unsigned int bufwidth, unsigned int offx, unsigned int offy, color_t fg, color_t bg){ unsigned int bitmap_row_width = g->width/8; uint8_t *bitmap = ((uint8_t *)g) + sizeof(glyph_t); for(unsigned int y=0; y < g->height; y++){ @@ -16,7 +16,8 @@ void render_glyph(glyph_t *g, uint8_t *buf, unsigned int bufwidth, unsigned int } uint8_t *p = buf + (offy+y)*bufwidth + offx; for(unsigned int x=0; x < g->width; x++){ - *p++ = (data&(1<<(g->width-1))) ? 1 : 0; + color_t c = (data&(1<<(g->width-1))) ? fg : bg; + *((color_t *)(p++)) = c; data <<= 1; } } diff --git a/host/matelight/font.h b/host/matelight/font.h index e3e2e60..d21100c 100644 --- a/host/matelight/font.h +++ b/host/matelight/font.h @@ -4,6 +4,7 @@ #include <stdint.h> #include <stdio.h> +#include "color.h" // CAUTION: A glyph struct is always followed by the glyph's bitmap. typedef struct { @@ -16,10 +17,12 @@ typedef struct { // Size of Unicode's basic multilingual plane #define BLP_SIZE 65536 +#define MAX_CSI_ELEMENTS 8 + // We could also use some fancy hashtable here, but unifont includes about 57k glyphs so we would hardly save any memory. int read_bdf(FILE *f, glyph_t **glyph_table, unsigned int glyph_table_size); // Requires buf to point to a buffer at least of size glyph->width*glyph->height. -void render_glyph(glyph_t *glyph, uint8_t *buf, unsigned int bufwidth, unsigned int offx, unsigned int offy); +void render_glyph(glyph_t *glyph, uint8_t *buf, unsigned int bufwidth, unsigned int offx, unsigned int offy, color_t fg, color_t bg); #endif//__FONT_H__ diff --git a/host/matelight/font.py b/host/matelight/font.py deleted file mode 100644 index a1c1dd6..0000000 --- a/host/matelight/font.py +++ /dev/null @@ -1,5 +0,0 @@ -from bdflib import reader as bdfreader # Used to read the bitmap font - -FONT = bdfreader.read_bdf(iter(open('fonts/unifont-6.3.20131020.bdf').readlines())) -FONT_HEIGHT = 16 - diff --git a/host/matelight/genpal.py b/host/matelight/genpal.py new file mode 100755 index 0000000..01fe611 --- /dev/null +++ b/host/matelight/genpal.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 + +xterm_colors = [ + (0x00, 0x00, 0x00), + (0xa8, 0x00, 0x00), + (0x00, 0xa8, 0x00), + (0xa8, 0x54, 0x00), + (0x00, 0x00, 0xa8), + (0xa8, 0x00, 0xa8), + (0x00, 0xa8, 0xa8), + (0xa8, 0xa8, 0xa8), + (0x54, 0x54, 0x54), + (0xfc, 0x54, 0x54), + (0x54, 0xfc, 0x54), + (0xfc, 0xfc, 0x54), + (0x54, 0x54, 0xfc), + (0xfc, 0x54, 0xfc), + (0x54, 0xfc, 0xfc), + (0xfc, 0xfc, 0xfc)] + +# colors 16..232: the 6x6x6 color cube +_valuerange = (0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff) +for i in range(217): + r = _valuerange[(i // 36) % 6] + g = _valuerange[(i // 6) % 6] + b = _valuerange[i % 6] + xterm_colors.append((r, g, b)) + +# colors 233..253: grayscale +for i in range(1, 24): + v = 8 + i * 10 + xterm_colors.append((v, v, v)) + +for r,g,b in xterm_colors: + print("\t{{{:#04x}, {:#04x}, {:#04x}}},".format(r,g,b)) diff --git a/host/matelight/host.py b/host/matelight/host.py deleted file mode 100644 index 4fa8578..0000000 --- a/host/matelight/host.py +++ /dev/null @@ -1,19 +0,0 @@ -import usb -import colorsys -import numpy as np -from config import * -import itertools - -dev = usb.core.find(idVendor=0x1cbe, idProduct=0x0003) - -def sendframe(framedata): - # not isinstance(framedata, np.array) or - if framedata.shape != (DISPLAY_HEIGHT, DISPLAY_WIDTH, 3) or framedata.dtype != np.uint8: - raise ValueError('framedata must be a ({}, {}, 3)-numpy array of uint8s. Got a {}-numpy array of {}'.format(DISPLAY_HEIGHT, DISPLAY_WIDTH, framedata.shape, framedata.dtype)) - - for cy, cx in itertools.product(range(CRATES_Y), range(CRATES_X)): - cratedata = framedata[cy*CRATE_HEIGHT:(cy+1)*CRATE_HEIGHT, cx*CRATE_WIDTH:(cx+1)*CRATE_WIDTH] - # Send framebuffer data - dev.write(0x01, bytes([0, cx, cy])+bytes(list(cratedata.flatten()))) - # Send latch command - dev.write(0x01, b'\x01') diff --git a/host/matelight/listeners.py b/host/matelight/listeners.py deleted file mode 100755 index f390389..0000000 --- a/host/matelight/listeners.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python3 -from socketserver import * -import socket -import threading -import zlib -import random -import struct -import host -import numpy as np -import time -import sys -import traceback -import renderers -from PIL import Image, ImageSequence -from config import * -# Loading frame (for the big font file) -img = Image.open(open('../nyancat.png', 'rb')) -frame = np.array(img.convert('RGB').getdata(), dtype=np.uint8).reshape((DISPLAY_HEIGHT, DISPLAY_WIDTH, 3)) -host.sendframe(frame) -from font import * - -UDP_THRES = 1.0 - -class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass -class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass - -default_renderers = [renderers.TextRenderer('\x1B[92mMate Light\x1B[93m@\x1B[92mPlay store or \x1B[94;101mtcp://ml.jaseg.net:1337\x1B[0;91m ♥ '), - renderers.TextRenderer('\x1B[92mMate Light\x1B[0;91m ♥ \x1B[92mUnicode'), - renderers.TextRenderer('\x1B[92mMate Light\x1B[0m powered by \x1B[95mMicrosoft™ \x1B[96mMarquee Manager® Pro')] -global renderer, count -renderer = default_renderers[0] -count = 0 -lastudp = 0 - -class MateLightUDPHandler(BaseRequestHandler): - def handle(self): - try: - global lastudp - data = self.request[0].strip() - if len(data) != FRAME_SIZE+4: - #raise ValueError('Invalid frame size: Expected {}, got {}'.format(FRAME_SIZE+4, len(data))) - return - frame = data[:-4] - crc1, = struct.unpack('!I', data[-4:]) - crc2, = zlib.crc32(frame, 0), - #if crc1 != crc2: - # raise ValueError('Invalid frame CRC checksum: Expected {}, got {}'.format(crc2, crc1)) - #socket.sendto(b'ACK', self.client_address) - a = np.array(list(frame), dtype=np.uint8) - lastudp = time.time() - host.sendframe(a.reshape((DISPLAY_HEIGHT, DISPLAY_WIDTH, 3))) - except Exception as e: - print('Error receiving UDP frame:', e) - ex_type, ex, tb = sys.exc_info() - traceback.print_tb(tb) - -class MateLightTCPTextHandler(BaseRequestHandler): - def handle(self): - try: - data = str(self.request.recv(1024).strip(), 'UTF-8') - if len(data) > 140: - self.request.sendall('TOO MUCH INFORMATION!\n') - return - global renderer, count - print(data+'\x1B[0m') - renderer = renderers.TextRenderer(data) - count = 3 - self.request.sendall(b'KTHXBYE!\n') - except: - pass - -TCPServer.allow_reuse_address = True -server = TCPServer(('', 1337), MateLightTCPTextHandler) -t = threading.Thread(target=server.serve_forever) -t.daemon = True -t.start() - -UDPServer.allow_reuse_address = True -userver = UDPServer(('', 1337), MateLightUDPHandler) -t = threading.Thread(target=userver.serve_forever) -t.daemon = True -t.start() - -while True: - global renderer, count, lastudp - foo = renderer - if count == 0: - renderer = random.choice(default_renderers) - else: - count = count - 1 - for frame, delay in foo.frames(): - #print(list(frame.flatten())) - now = time.time() - if now-lastudp > UDP_THRES: - host.sendframe(np.swapaxes(frame, 0, 1)) - else: - time.sleep(0.1) - diff --git a/host/matelight/main.c b/host/matelight/main.c index 7a23a0a..48c50d9 100644 --- a/host/matelight/main.c +++ b/host/matelight/main.c @@ -1,5 +1,6 @@ #include "font.h" +#include "color.h" #include <stdint.h> #include <stdlib.h> #include <stdio.h> @@ -7,6 +8,7 @@ #include <string.h> #include <wchar.h> #include <locale.h> +#include <sys/timeb.h> /* CAUTION: REQUIRES INPUT TO BE \0-TERMINATED */ int console_render(char *s, glyph_t **glyph_table, unsigned int glyph_table_size){ @@ -53,7 +55,8 @@ int console_render(char *s, glyph_t **glyph_table, unsigned int glyph_table_size // For easier rendering on the terminal, round up to multiples of two gbufheight += gbufheight&1; - unsigned int gbufsize = gbufwidth*gbufheight; + // gbuf uses 3 bytes per color for r, g and b + unsigned int gbufsize = gbufwidth*3*gbufheight; gbuf = malloc(gbufsize); if(gbuf == 0){ fprintf(stderr, "Cannot malloc() %d bytes.\n", gbufsize); @@ -64,31 +67,260 @@ int console_render(char *s, glyph_t **glyph_table, unsigned int glyph_table_size unsigned int x = 0; p = s; memset(&ps, 0, sizeof(mbstate_t)); + struct { + color_t fg; + color_t bg; + int blink:4; + int bold:1; // TODO + int underline:1; + int strikethrough:1; + int fraktur:1; // TODO See: Flat10 Fraktur font + int invert:1; + } style = { + colortable[DEFAULT_FG_COLOR], colortable[DEFAULT_BG_COLOR], 0, 0, 0, 0, 0, 0 + }; for(;;){ + // NOTE: This nested escape sequence parsing does not contain any unicode-awareness whatsoever + if(*p == '\033'){ // Escape sequence YAY + char *sequence_start = p++; + if(*p == '['){ // This was a CSI! + long elems[MAX_CSI_ELEMENTS]; + int nelems; + for(nelems = 0; nelems<MAX_CSI_ELEMENTS; nelems++){ + p++; + char *endptr; + elems[nelems] = strtol(p, &endptr, 10); + if(p == endptr){ + fprintf(stderr, "Invalid ANSI escape code: \"\\e%s\"\n", sequence_start); + goto error; + } + if(*endptr == 'm') + break; + if(*endptr != ';'){ + fprintf(stderr, "Invalid ANSI escape code: \"\\e%s\"\n", sequence_start); + goto error; + } + } + // By now we know it's a SGR since we error'ed out on anything else + if(nelems < 1){ + fprintf(stderr, "Unsupported escape sequence: \"\\e%s\"\n", sequence_start); + goto error; + } + for(int i=0; i<nelems; i++){ + switch(elems[i]){ + case 0: // reset style + style.fg = colortable[DEFAULT_FG_COLOR]; + style.bg = colortable[DEFAULT_BG_COLOR]; + style.bold = 0; + style.underline = 0; + style.blink = 0; + style.strikethrough = 0; + style.fraktur = 0; + style.invert = 0; + break; + case 1: // bold + style.bold = 1; + break; + case 4: // underline + style.underline = 1; + break; + case 5: // slow blink + style.blink = 1; + break; + case 6: // rapid blink + style.blink = 8; + break; + case 7: // color invert on + style.invert = 1; + break; + case 9: // strike-through + style.strikethrough = 1; + break; + case 20:// Fraktur + style.fraktur = 1; + break; + case 22:// Bold off + style.bold = 0; + break; + case 24:// Underline off + style.underline = 0; + break; + case 25:// Blink off + style.blink = 0; + break; + case 27:// color invert off + style.invert = 0; + break; + case 29:// strike-through off + style.strikethrough = 0; + break; + case 30: // Set foreground color, "dim" colors + case 31: + case 32: + case 33: + case 34: + case 35: + case 36: + case 37: + style.fg = colortable[elems[i]-30]; + break; + case 38: // Set xterm-256 foreground color + i++; + if(nelems-i < 2 || elems[i] != 5){ + fprintf(stderr, "Invalid ANSI escape code: \"\\e%s\"\n", sequence_start); + goto error; + } + style.fg = colortable[elems[i++]]; + break; + case 39: // Reset foreground color to default + style.bg = colortable[DEFAULT_FG_COLOR]; + break; + case 40: // Set background color, "dim" colors + case 41: + case 42: + case 43: + case 44: + case 45: + case 46: + case 47: + style.bg = colortable[elems[i]-40]; + break; + case 48: // Set xterm-256 background color + i++; + if(nelems-i < 2 || elems[i] != 5){ + fprintf(stderr, "Invalid ANSI escape code: \"\\e%s\"\n", sequence_start); + goto error; + } + style.bg = colortable[elems[i++]]; + break; + case 49: // Reset background color to default + style.bg = colortable[DEFAULT_BG_COLOR]; + break; + case 90: // Set foreground color, "bright" colors + case 91: + case 92: + case 93: + case 94: + case 95: + case 96: + case 97: + style.fg = colortable[elems[i]-90+8]; + break; + case 100: // Set background color, "bright" colors + case 101: + case 102: + case 103: + case 104: + case 105: + case 106: + case 107: + style.bg = colortable[elems[i]-100+8]; + break; + default: + fprintf(stderr, "Unsupported escape sequence: \"\\e%s\"\n", sequence_start); + goto error; + } + } + + }else{ + fprintf(stderr, "Unsupported escape sequence: \"\\e%s\"\n", sequence_start); + goto error; + } + + continue; + } + size_t inc = mbrtowc(&c, p, (s+len+1)-p, NULL); // If p contained if(inc == 0) // Reached end of string break; p += inc; + struct timeb time = {0}; + ftime(&time); + unsigned long int t = time.time*1000 + time.millitm; + int blink = style.blink && (t % (1000/style.blink) < (333/style.blink)); + int inv = style.invert ^ blink; + color_t fg = inv ? style.fg : style.bg; + color_t bg = inv ? style.bg : style.fg; + + printf("Rendering glyph %lc: Strikethrough %d Underline %d Blink %d Inverted %d Bold %d Fraktur %d FG (%d, %d, %d) BG (%d, %d, %d)\n", + c, + style.strikethrough, + style.underline, + style.blink, + style.invert, + style.bold, + style.fraktur, + style.fg.r, + style.fg.g, + style.fg.b, + style.bg.r, + style.bg.g, + style.bg.b); glyph_t *g = glyph_table[c]; - render_glyph(g, gbuf, gbufwidth, x, 0); + render_glyph(g, gbuf, gbufwidth, x, 0, fg, bg); + if(style.strikethrough || style.underline){ + int sty = gbufheight/2; + // g->y usually is a negative index of the glyph's baseline measured from the glyph's bottom + int uly = gbufheight + g->y; + for(int i=0; i<g->width; i++){ + if(style.strikethrough) + *((color_t *)(gbuf + (sty*gbufwidth + i)*3)) = fg; + if(style.underline) + *((color_t *)(gbuf + (uly*gbufwidth + i)*3)) = fg; + } + } x += g->width; } + for(unsigned int y=0; y < gbufheight; y++){ + for(unsigned int x=0; x < gbufwidth; x++){ + color_t c = *((color_t *)(gbuf + (y*gbufwidth + x)*3)); + //printf("\033[0m%02x,%02x,%02x-%02x\033[38;5;%dm█", c.r, c.g, c.b, xterm_color_index(c), xterm_color_index(c)); + printf("\033[38;5;%dm█", xterm_color_index(c)); + } + printf("\n"); + } + return 0; + color_t lastfg = {0, 0, 0}, lastbg = {0, 0, 0}; + printf("\e[38;5;0;48;5;0m"); for(unsigned int y=0; y < gbufheight; y+=2){ for(unsigned int x=0; x < gbufwidth; x++){ //Da magicks: ▀█▄ - char c1 = gbuf[y*gbufwidth + x]; - char c2 = gbuf[(y+1)*gbufwidth + x]; - if(c1 && c2) - printf("█"); - else if(c1 && !c2) - printf("▀"); - else if(!c1 && c2) - printf("▄"); - else - printf(" "); + color_t ct = *((color_t *)(gbuf + (y*gbufwidth + x)*3)); // Top pixel + color_t cb = *((color_t *)(gbuf + ((y+1)*gbufwidth + x)*3)); // Bottom pixel + if(!memcmp(&ct, &lastfg, sizeof(color_t))){ + if(!memcmp(&cb, &lastbg, sizeof(color_t))){ + printf("▀"); + }else if(!memcmp(&cb, &lastfg, sizeof(color_t))){ + printf("█"); + }else{ + printf("\033[48;5;%dm▀", xterm_color_index(cb)); + lastbg = cb; + } + }else if(!memcmp(&ct, &lastbg, sizeof(color_t))){ + if(!memcmp(&cb, &lastfg, sizeof(color_t))){ + printf("▄"); + }else if(!memcmp(&cb, &lastbg, sizeof(color_t))){ + printf(" "); + }else{ + printf("\033[38;5;%dm▄", xterm_color_index(cb)); + lastfg = cb; + } + }else{ // No matches for the upper pixel + if(!memcmp(&cb, &lastfg, sizeof(color_t))){ + printf("\033[48;5;%dm▄", xterm_color_index(ct)); + lastbg = ct; + }else if(!memcmp(&cb, &lastbg, sizeof(color_t))){ + printf("\033[38;5;%dm▀", xterm_color_index(ct)); + lastfg = ct; + }else{ + printf("\033[38;5;%d;48;5;%dm▀", xterm_color_index(ct), xterm_color_index(cb)); + lastfg = ct; + lastbg = cb; + } + } } printf("\n"); } diff --git a/host/matelight/nyancat-test.py b/host/matelight/nyancat-test.py deleted file mode 100755 index 73fd28e..0000000 --- a/host/matelight/nyancat-test.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python3 -import host -import numpy as np -from config import * -from PIL import Image, ImageSequence -import time - -img1 = Image.open(open('../nyancat.png', 'rb')) -img2 = Image.open(open('../nyancat2.png', 'rb')) -scroller = Image.open(open('../scroller.png', 'rb')) -datas = [] -for img in [img1, img2]: - im = img.convert("RGB") - im.thumbnail((DISPLAY_WIDTH, DISPLAY_HEIGHT), Image.NEAREST) - data = np.array(im.getdata(), dtype=np.uint8) - datas += [data.reshape((DISPLAY_HEIGHT, DISPLAY_WIDTH, 3))] - -im = scroller.convert("RGB") -bar = np.array(im.getdata(), dtype=np.uint8) -foo = bar.reshape((DISPLAY_HEIGHT, 300, 3)) - -while True: - for i in range(60): - for data in datas: - host.sendframe(data) - time.sleep(0.1) - for i in range(260): - host.sendframe(foo[:, i:i+40, :]) - diff --git a/host/matelight/queuemgr.py b/host/matelight/queuemgr.py deleted file mode 100644 index df75309..0000000 --- a/host/matelight/queuemgr.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env python3 - -from renderers import TextRenderer, ImageRenderer -import host, config -import time, math - -score = lambda now, last, lifetime, priority, item: priority*math.log(now-last)/(10.0+item.duration) - -class FuzzyQueue: - def __init__(self, default): - self._default = default - self.put(default, 0.0, 0) - self._l = [] - - def put(self, item, priority=1.0, lifetime=0): - lifetime += time.time() - self._l.append((0, lifetime, priority, item)) - - def pop(self): - """ Get an item from the queue - - NOTE: This is *not* a regular pop, as it does not necessarily remove the item from the queue. - """ - now = time.time() - # Choose item based on last used and priority - _, index, (_, lifetime, priority, item) = max(sorted([(score(now, *v), i, v) for i, v in self._l])) - # If item's lifetime is exceeded, remove - if lifetime < now and item is not self._default: - del self._l[index] - # Otherwise, set item's last played time - self._l[index] = (now, lifetime, prioity, item) - # Finally, return - return item - -q = FuzzyQueue() - -def insert_text(text, priority=1.0, lifetime=0, escapes=True): - q.put(TextRenderer(text, escapes), priority, lifetime) - -def insert_image(image, priority=1.0, lifetime=0): - q.put(ImageRenderer(image), priority, lifetime) - -def render_thread(): - while True: - start = time.time() - for frame, delay in q.pop().frames(start): - then = time.time() - if then-start+delay > RENDERER_TIMEOUT: - break - sendframe(frame) - now = time.time() - time.sleep(min(RENDERER_TIMEOUT, delay - (now-then))) diff --git a/host/matelight/renderers.py b/host/matelight/renderers.py deleted file mode 100644 index f507513..0000000 --- a/host/matelight/renderers.py +++ /dev/null @@ -1,154 +0,0 @@ -import time -try: - import re2 as re -except ImportError: - import re -import numpy as np -from PIL import Image -from pixelterm import xtermcolors -from config import * -from font import * - -default_palette = [ - (0x00, 0x00, 0x00), # 0 normal colors - (0xcd, 0x00, 0x00), # 1 - (0x00, 0xcd, 0x00), # 2 - (0xcd, 0xcd, 0x00), # 3 - (0x00, 0x00, 0xee), # 4 - (0xcd, 0x00, 0xcd), # 5 - (0x00, 0xcd, 0xcd), # 6 - (0xe5, 0xe5, 0xe5), # 7 - (0x7f, 0x7f, 0x7f), # 8 bright colors - (0xff, 0x00, 0x00), # 9 - (0x00, 0xff, 0x00), # 10 - (0xff, 0xff, 0x00), # 11 - (0x5c, 0x5c, 0xff), # 12 - (0xff, 0x00, 0xff), # 13 - (0x00, 0xff, 0xff), # 14 - (0xff, 0xff, 0xff)] # 15 -default_colors = (default_palette[8], default_palette[0]) - -class CharGenerator: - def __init__(self, seq=None, lg=None, text=''): - settings = False, False, False, default_colors - if lg: - settings = lg.bold, lg.blink, lg.underscore, (lg.fg, lg.bg) - self.bold, self.blink, self.underscore, (self.fg, self.bg) = settings - self.text = text - if seq: - self.parse_escape_sequence(seq) - - def parse_escape_sequence(self, seq): - codes = list(map(int, seq[2:-1].split(';'))) - fg, bg, reverse, i = self.fg, self.bg, False, 0 - while i<len(codes): - a = codes[i] - if a in [38, 48]: - if codes[i+1] == 5: - c = xtermcolors.xterm_colors[codes[i+2]] - fg, bg = (c, bg) if a == 38 else (fg, c) - i += 2 - elif a == 39: - fg = (0,0,0) - elif a == 49: - bg = (0,0,0) - elif a == 0: - fg, bg = default_colors - self.bold, self.blink, self.underscore = False, False, False - elif a in range(30, 38): - fg = default_palette[a-30] - elif a in range(90, 98): - fg = default_palette[a-90+8] - elif a in range(40, 48): - bg = default_palette[a-40] - elif a in range(101, 108): - bg = default_palette[a-100+8] - elif a == 7: - reverse = True - elif a == 5: - self.blink = True - elif a == 4: - self.underscore = True - elif a == 1: # Literally "bright", not bold. - self.bold = True - i += 1 - fg, bg = (bg, fg) if reverse else (fg, bg) - self.fg, self.bg = fg, bg - - def generate_char(self, c, now): - fg, bg = (self.bg, self.fg) if self.blink and now%1.0 < 0.3 else (self.fg, self.bg) - glyph = FONT.glyphs_by_codepoint[ord(c)] - # Please forgive the string manipulation below. - lookup = {'0': bg, '1': fg} - FONT_PADDED_BINARY = ('{:0'+str(glyph.bbW)+'b}').format - FONT_Y_PAD = [[bg]*glyph.bbW]*(DISPLAY_HEIGHT-FONT_HEIGHT) - return np.swapaxes(np.array([ list(map(lookup.get, FONT_PADDED_BINARY(int(row, 16))[:glyph.bbW])) for row in glyph.get_data() ] + FONT_Y_PAD, dtype=np.uint8), 0, 1) - - def generate(self, now): - chars = [self.generate_char(c, now) for c in self.text] - # This refers to inter-letter spacing - space = np.zeros((LETTER_SPACING, DISPLAY_HEIGHT, 3), dtype=np.uint8) - spaces = [space]*(len(chars)-1) - everything = chars + spaces - everything[::2] = chars - everything[1::2] = spaces - return np.concatenate(everything) - -class TextRenderer: - def __init__(self, text, escapes=True): - """Renders text into a frame buffer - - "escapes" tells the renderer whether to interpret escape sequences (True) or not (False). - """ - generators = [] - current_generator = CharGenerator() - for match in re.finditer('(\x1B\[[0-9;]+m)|(.)', text): - esc, char = match.groups() - if esc: - if current_generator.text != '': - generators.append(current_generator) - current_generator = CharGenerator(esc, current_generator) - elif char: - current_generator.text += char - generators = generators + [current_generator] - # Generate the actual frame buffer - zeros = [np.zeros((DISPLAY_WIDTH, DISPLAY_HEIGHT, 3), dtype=np.uint8)] - # Pad the array with one screen's worth of zeros on both sides so the text fully scrolls through. - now = time.time() - self.raw = np.concatenate(zeros+[g.generate(now) for g in generators]+zeros) - - def frames(self): - w,h,_ = self.raw.shape - for i in range(0, w-DISPLAY_WIDTH, 2): - frame = self.raw[i:i+DISPLAY_WIDTH, :, :] - yield frame, 1/DEFAULT_SCROLL_SPEED - -class ImageRenderer: - def __new__(cls, image_data): - img = Image.open(io.BytesIO(image_data)) - self.img = img - - def frames(self): - img = self.img - palette = img.getpalette() - last_frame = Image.new("RGB", img.size) - # FIXME set delay to 1/10s if the image is animated, only use DEFAULT_IMAGE_DURATION for static images. - delay = img.info.get('duration', DEFAULT_IMAGE_DURATION*1000.0)/1000.0 - - for frame in ImageSequence.Iterator(img): - #This works around a known bug in Pillow - #See also: http://stackoverflow.com/questions/4904940/python-converting-gif-frames-to-png - frame.putpalette(palette) - c = frame.convert("RGB") - - if img.info['background'] != img.info['transparency']: - last_frame.paste(c, c) - else: - last_frame = c - - im = last_frame.copy() - im.thumbnail((DISPLAY_WIDTH, DISPLAY_HEIGHT), Image.NEAREST) - data = np.array(im.getdata(), dtype=np.int8) - data.reshape((DISPLAY_WIDTH, DISPLAY_HEIGHT, 3)) - yield data, delay - diff --git a/host/matelight/scroll-test.py b/host/matelight/scroll-test.py deleted file mode 100755 index f962416..0000000 --- a/host/matelight/scroll-test.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python3 -import host -import numpy as np -from config import * -import time -import renderers - -while True: - renderer = renderers.TextRenderer('\x1B[91mThe \x1B[92;105mquick\x1B[0m brown \x1B[96;5mfox jumps over\x1B[0m the lazy dog.') - for frame, delay in renderer.frames(): - #print(list(frame.flatten())) - host.sendframe(np.swapaxes(frame, 0, 1)) - #time.sleep(delay) - |