diff --git a/.gitignore b/.gitignore index b8fb39c..78d9050 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ img2cpi img2cpi.o wsvpn wsvpn.o +cpi2png +cc-common.o +vim.state +__pycache__ diff --git a/Makefile b/Makefile index 21e8cf0..189f517 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,19 @@ CPPFLAGS += -Idependencies -Idependencies/mongoose LDLIBS += -lm -all: img2cpi wsvpn +all: img2cpi cpi2png wsvpn + +test-cpi2png: cpi2png + ./cpi2png ./cpi-images/rat.cpi /tmp/rat.png + +img2cpi: img2cpi.c cc-common.o dependencies/stb/stb_image.o dependencies/stb/stb_image_resize2.o + $(CC) $(LDFLAGS) $^ $(LDLIBS) -o "$@" $(CPPFLAGS) + +cpi2png: cpi2png.c cc-common.o dependencies/stb/stb_image_write.o + $(CC) $(LDFLAGS) $^ $(LDLIBS) -o "$@" $(CPPFLAGS) + +dependencies/stb/%.o: dependencies/stb/%.h + $(CC) -DSTB_IMAGE_IMPLEMENTATION -DSTB_IMAGE_RESIZE_IMPLEMENTATION -DSTB_IMAGE_WRITE_IMPLEMENTATION -x c $^ -c -o "$@" wsvpn: wsvpn.o dependencies/mongoose/mongoose.o $(CC) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o "$@" diff --git a/cc-common.c b/cc-common.c new file mode 100644 index 0000000..46e19e8 --- /dev/null +++ b/cc-common.c @@ -0,0 +1,296 @@ +#include "cc-common.h" + +const struct palette cc_default_palette = PALETTE( + { { 0xf0, 0xf0, 0xf0, 0xff } }, + { { 0xf2, 0xb2, 0x33, 0xff } }, + { { 0xe5, 0x7f, 0xd8, 0xff } }, + { { 0x99, 0xb2, 0xf2, 0xff } }, + { { 0xde, 0xde, 0x6c, 0xff } }, + { { 0x7f, 0xcc, 0x19, 0xff } }, + { { 0xf2, 0xb2, 0xcc, 0xff } }, + { { 0x4c, 0x4c, 0x4c, 0xff } }, + { { 0x99, 0x99, 0x99, 0xff } }, + { { 0x4c, 0x99, 0xb2, 0xff } }, + { { 0xb2, 0x66, 0xe5, 0xff } }, + { { 0x33, 0x66, 0xcc, 0xff } }, + { { 0x7f, 0x66, 0x4c, 0xff } }, + { { 0x57, 0xa6, 0x4e, 0xff } }, + { { 0xcc, 0x4c, 0x4c, 0xff } }, + { { 0x11, 0x11, 0x11, 0xff } } +), cc_default_gray_palette = PALETTE( + { { 0xf0, 0xf0, 0xf0, 0xff } }, + { { 0x9d, 0x9d, 0x9d, 0xff } }, + { { 0xbe, 0xbe, 0xbe, 0xff } }, + { { 0xbf, 0xbf, 0xbf, 0xff } }, + { { 0xb8, 0xb8, 0xb8, 0xff } }, + { { 0x76, 0x76, 0x76, 0xff } }, + { { 0xd0, 0xd0, 0xd0, 0xff } }, + { { 0x4c, 0x4c, 0x4c, 0xff } }, + { { 0x99, 0x99, 0x99, 0xff } }, + { { 0x87, 0x87, 0x87, 0xff } }, + { { 0xa9, 0xa9, 0xa9, 0xff } }, + { { 0x77, 0x77, 0x77, 0xff } }, + { { 0x65, 0x65, 0x65, 0xff } }, + { { 0x6e, 0x6e, 0x6e, 0xff } }, + { { 0x76, 0x76, 0x76, 0xff } }, + { { 0x11, 0x11, 0x11, 0xff } } +); + +const GlyphBitmap cc_font_atlas[256] = { + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x44, 0x6c, 0x44, 0x54, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x7c, 0x54, 0x7c, 0x44, 0x6c, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x28, 0x7c, 0x7c, 0x7c, 0x38, 0x10, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x10, 0x38, 0x7c, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x10, 0x38, 0x10, 0x7c, 0x7c, 0x10, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x10, 0x38, 0x7c, 0x7c, 0x10, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x18, 0x3c, 0x3c, 0x18, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x7e, 0x7e, 0x66, 0x42, 0x42, 0x66, 0x7e, 0x7e, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x1c, 0x0c, 0x34, 0x48, 0x48, 0x30, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x44, 0x44, 0x44, 0x38, 0x10, 0x38, 0x10, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x3c, 0x24, 0x3c, 0x20, 0x60, 0x60, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x3e, 0x22, 0x3e, 0x22, 0x66, 0x66, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x40, 0x70, 0x7c, 0x70, 0x40, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x04, 0x1c, 0x7c, 0x1c, 0x04, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x10, 0x38, 0x7c, 0x10, 0x10, 0x7c, 0x38, 0x10, 0x00, 0x00, }, + { 0x00, 0x24, 0x24, 0x24, 0x24, 0x24, 0x00, 0x24, 0x00, 0x00, 0x00, }, + { 0x00, 0x3c, 0x54, 0x54, 0x34, 0x14, 0x14, 0x14, 0x00, 0x00, 0x00, }, + { 0x00, 0x3c, 0x60, 0x58, 0x44, 0x34, 0x0c, 0x78, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x3c, 0x00, 0x00, 0x00, }, + { 0x00, 0x10, 0x38, 0x7c, 0x10, 0x7c, 0x38, 0x10, 0x7c, 0x00, 0x00, }, + { 0x00, 0x10, 0x38, 0x7c, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, + { 0x00, 0x10, 0x10, 0x10, 0x10, 0x7c, 0x38, 0x10, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x10, 0x18, 0x7c, 0x18, 0x10, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x10, 0x30, 0x7c, 0x30, 0x10, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x7c, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x24, 0x7e, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x10, 0x10, 0x38, 0x38, 0x7c, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x7c, 0x38, 0x38, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, }, + { 0x00, 0x14, 0x14, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x28, 0x28, 0x7c, 0x28, 0x7c, 0x28, 0x28, 0x00, 0x00, 0x00, }, + { 0x00, 0x10, 0x3c, 0x40, 0x38, 0x04, 0x78, 0x10, 0x00, 0x00, 0x00, }, + { 0x00, 0x44, 0x48, 0x08, 0x10, 0x20, 0x24, 0x44, 0x00, 0x00, 0x00, }, + { 0x00, 0x10, 0x28, 0x10, 0x34, 0x58, 0x48, 0x34, 0x00, 0x00, 0x00, }, + { 0x00, 0x08, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x0c, 0x10, 0x20, 0x20, 0x20, 0x10, 0x0c, 0x00, 0x00, 0x00, }, + { 0x00, 0x30, 0x08, 0x04, 0x04, 0x04, 0x08, 0x30, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x24, 0x18, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x10, 0x10, 0x7c, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, }, + { 0x00, 0x04, 0x08, 0x08, 0x10, 0x20, 0x20, 0x40, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x44, 0x4c, 0x54, 0x64, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x10, 0x30, 0x10, 0x10, 0x10, 0x10, 0x7c, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x44, 0x04, 0x18, 0x20, 0x44, 0x7c, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x44, 0x04, 0x18, 0x04, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x0c, 0x14, 0x24, 0x44, 0x7c, 0x04, 0x04, 0x00, 0x00, 0x00, }, + { 0x00, 0x7c, 0x40, 0x78, 0x04, 0x04, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x18, 0x20, 0x40, 0x78, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x7c, 0x44, 0x04, 0x08, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x44, 0x44, 0x38, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x44, 0x44, 0x3c, 0x04, 0x08, 0x30, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x10, 0x10, 0x00, 0x00, }, + { 0x00, 0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x20, 0x10, 0x08, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x44, 0x04, 0x08, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, }, + { 0x00, 0x3c, 0x42, 0x5a, 0x5a, 0x5e, 0x40, 0x3c, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x44, 0x7c, 0x44, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, }, + { 0x00, 0x78, 0x44, 0x78, 0x44, 0x44, 0x44, 0x78, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x44, 0x40, 0x40, 0x40, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x78, 0x44, 0x44, 0x44, 0x44, 0x44, 0x78, 0x00, 0x00, 0x00, }, + { 0x00, 0x7c, 0x40, 0x70, 0x40, 0x40, 0x40, 0x7c, 0x00, 0x00, 0x00, }, + { 0x00, 0x7c, 0x40, 0x70, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, }, + { 0x00, 0x3c, 0x40, 0x4c, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x44, 0x44, 0x7c, 0x44, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x44, 0x48, 0x70, 0x48, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, }, + { 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7c, 0x00, 0x00, 0x00, }, + { 0x00, 0x44, 0x6c, 0x54, 0x44, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, }, + { 0x00, 0x44, 0x64, 0x54, 0x4c, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x78, 0x44, 0x78, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x44, 0x44, 0x44, 0x44, 0x48, 0x34, 0x00, 0x00, 0x00, }, + { 0x00, 0x78, 0x44, 0x78, 0x44, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, }, + { 0x00, 0x3c, 0x40, 0x38, 0x04, 0x04, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x7c, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, + { 0x00, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x44, 0x44, 0x44, 0x44, 0x28, 0x28, 0x10, 0x00, 0x00, 0x00, }, + { 0x00, 0x44, 0x44, 0x44, 0x44, 0x54, 0x6c, 0x44, 0x00, 0x00, 0x00, }, + { 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, }, + { 0x00, 0x44, 0x28, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, + { 0x00, 0x7c, 0x04, 0x08, 0x10, 0x20, 0x40, 0x7c, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x20, 0x20, 0x20, 0x20, 0x20, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x40, 0x20, 0x20, 0x10, 0x08, 0x08, 0x04, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x08, 0x08, 0x08, 0x08, 0x08, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, }, + { 0x00, 0x10, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x38, 0x04, 0x3c, 0x44, 0x3c, 0x00, 0x00, 0x00, }, + { 0x00, 0x40, 0x40, 0x58, 0x64, 0x44, 0x44, 0x78, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x38, 0x44, 0x40, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x04, 0x04, 0x34, 0x4c, 0x44, 0x44, 0x3c, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x38, 0x44, 0x7c, 0x40, 0x3c, 0x00, 0x00, 0x00, }, + { 0x00, 0x0c, 0x10, 0x3c, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x3c, 0x44, 0x44, 0x3c, 0x04, 0x78, 0x00, 0x00, }, + { 0x00, 0x40, 0x40, 0x58, 0x64, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, }, + { 0x00, 0x10, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, + { 0x00, 0x04, 0x00, 0x04, 0x04, 0x04, 0x44, 0x44, 0x38, 0x00, 0x00, }, + { 0x00, 0x20, 0x20, 0x24, 0x28, 0x30, 0x28, 0x24, 0x00, 0x00, 0x00, }, + { 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x08, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x68, 0x54, 0x54, 0x44, 0x44, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x78, 0x44, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x58, 0x64, 0x44, 0x78, 0x40, 0x40, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x34, 0x4c, 0x44, 0x3c, 0x04, 0x04, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x58, 0x64, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x3c, 0x40, 0x38, 0x04, 0x78, 0x00, 0x00, 0x00, }, + { 0x00, 0x10, 0x10, 0x38, 0x10, 0x10, 0x10, 0x08, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x44, 0x44, 0x44, 0x44, 0x3c, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x44, 0x44, 0x44, 0x28, 0x10, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x44, 0x44, 0x54, 0x54, 0x3c, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x44, 0x44, 0x44, 0x3c, 0x04, 0x78, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x7c, 0x08, 0x10, 0x20, 0x7c, 0x00, 0x00, 0x00, }, + { 0x00, 0x0c, 0x10, 0x10, 0x20, 0x10, 0x10, 0x0c, 0x00, 0x00, 0x00, }, + { 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, + { 0x00, 0x30, 0x08, 0x08, 0x04, 0x08, 0x08, 0x30, 0x00, 0x00, 0x00, }, + { 0x00, 0x32, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x24, 0x48, 0x12, 0x24, 0x48, 0x12, 0x24, 0x48, 0x12, 0x00, }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0xfe, 0xfe, 0xfe, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, }, + { 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, }, + { 0x0f, 0x0f, 0x0f, 0x0f, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, }, + { 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, }, + { 0xf0, 0xf0, 0xf0, 0xf0, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, }, + { 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, }, + { 0xff, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, }, + { 0xf0, 0xf0, 0xf0, 0xf0, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, }, + { 0x0f, 0x0f, 0x0f, 0x0f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, }, + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, }, + { 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, }, + { 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, }, + { 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, }, + { 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, }, + { 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, }, + { 0x0f, 0x0f, 0x0f, 0x0f, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, }, + { 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, }, + { 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x0f, 0xf0, 0xf0, 0xf0, 0xf0, }, + { 0xf0, 0xf0, 0xf0, 0xf0, 0x0f, 0x0f, 0x0f, 0xf0, 0xf0, 0xf0, 0xf0, }, + { 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0xf0, 0xf0, 0xf0, 0xf0, }, + { 0x7f, 0x7f, 0x7f, 0x7f, 0x0f, 0x0f, 0x0f, 0xf0, 0xf0, 0xf0, 0xf0, }, + { 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, }, + { 0xf0, 0xf0, 0xf0, 0xf0, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, }, + { 0x0f, 0x0f, 0x0f, 0x0f, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, }, + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x10, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x10, 0x38, 0x44, 0x40, 0x44, 0x38, 0x10, 0x00, 0x00, }, + { 0x00, 0x18, 0x24, 0x20, 0x78, 0x20, 0x20, 0x7c, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x44, 0x38, 0x44, 0x44, 0x44, 0x38, 0x44, 0x00, 0x00, }, + { 0x00, 0x44, 0x28, 0x7c, 0x10, 0x7c, 0x10, 0x10, 0x00, 0x00, 0x00, }, + { 0x00, 0x10, 0x10, 0x10, 0x00, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, + { 0x00, 0x3c, 0x60, 0x58, 0x44, 0x34, 0x0c, 0x78, 0x00, 0x00, 0x00, }, + { 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x3c, 0x4a, 0x52, 0x52, 0x4a, 0x3c, 0x00, 0x00, 0x00, }, + { 0x00, 0x30, 0x08, 0x38, 0x48, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x14, 0x28, 0x50, 0x28, 0x14, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x00, 0x7c, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x3c, 0x5a, 0x5a, 0x56, 0x42, 0x3c, 0x00, 0x00, 0x00, }, + { 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x30, 0x48, 0x48, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x10, 0x10, 0x7c, 0x10, 0x10, 0x00, 0x7c, 0x00, 0x00, }, + { 0x00, 0x40, 0x20, 0x60, 0x40, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x60, 0x20, 0x60, 0x20, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x20, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x44, 0x44, 0x44, 0x44, 0x7a, 0x40, 0x40, 0x00, }, + { 0x00, 0x3c, 0x54, 0x54, 0x34, 0x14, 0x14, 0x14, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x00, 0x00, }, + { 0x00, 0x20, 0x60, 0x20, 0x20, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x50, 0x28, 0x14, 0x28, 0x50, 0x00, 0x00, 0x00, }, + { 0x00, 0x44, 0x48, 0x08, 0x10, 0x2c, 0x2c, 0x44, 0x00, 0x00, 0x00, }, + { 0x00, 0x44, 0x48, 0x08, 0x10, 0x24, 0x28, 0x4c, 0x00, 0x00, 0x00, }, + { 0x00, 0x64, 0x28, 0x68, 0x10, 0x2c, 0x2c, 0x44, 0x00, 0x00, 0x00, }, + { 0x00, 0x10, 0x00, 0x10, 0x20, 0x40, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x60, 0x00, 0x38, 0x44, 0x7c, 0x44, 0x44, 0x00, 0x00, 0x00, }, + { 0x00, 0x0c, 0x00, 0x38, 0x44, 0x7c, 0x44, 0x44, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x44, 0x38, 0x44, 0x7c, 0x44, 0x44, 0x00, 0x00, 0x00, }, + { 0x00, 0x28, 0x50, 0x38, 0x44, 0x7c, 0x44, 0x44, 0x00, 0x00, 0x00, }, + { 0x00, 0x28, 0x00, 0x38, 0x44, 0x7c, 0x44, 0x44, 0x00, 0x00, 0x00, }, + { 0x00, 0x10, 0x00, 0x38, 0x44, 0x7c, 0x44, 0x44, 0x00, 0x00, 0x00, }, + { 0x00, 0x3c, 0x50, 0x50, 0x78, 0x50, 0x50, 0x5c, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x44, 0x40, 0x40, 0x44, 0x38, 0x08, 0x10, 0x00, 0x00, }, + { 0x00, 0x60, 0x00, 0x7c, 0x40, 0x78, 0x40, 0x7c, 0x00, 0x00, 0x00, }, + { 0x00, 0x0c, 0x00, 0x7c, 0x40, 0x78, 0x40, 0x7c, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x44, 0x7c, 0x40, 0x78, 0x40, 0x7c, 0x00, 0x00, 0x00, }, + { 0x00, 0x28, 0x00, 0x7c, 0x40, 0x78, 0x40, 0x7c, 0x00, 0x00, 0x00, }, + { 0x00, 0x30, 0x00, 0x38, 0x10, 0x10, 0x10, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x18, 0x00, 0x38, 0x10, 0x10, 0x10, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x10, 0x28, 0x38, 0x10, 0x10, 0x10, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x28, 0x00, 0x38, 0x10, 0x10, 0x10, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x78, 0x44, 0x44, 0x64, 0x44, 0x44, 0x78, 0x00, 0x00, 0x00, }, + { 0x00, 0x14, 0x28, 0x44, 0x64, 0x54, 0x4c, 0x44, 0x00, 0x00, 0x00, }, + { 0x00, 0x60, 0x38, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x0c, 0x38, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x44, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x28, 0x50, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x28, 0x38, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x44, 0x4c, 0x54, 0x64, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x60, 0x00, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x0c, 0x00, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x10, 0x28, 0x00, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x28, 0x00, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x0c, 0x00, 0x44, 0x28, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x10, 0x18, 0x14, 0x18, 0x10, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x78, 0x44, 0x58, 0x44, 0x44, 0x44, 0x58, 0x40, 0x00, 0x00, }, + { 0x00, 0x60, 0x00, 0x38, 0x04, 0x3c, 0x44, 0x3c, 0x00, 0x00, 0x00, }, + { 0x00, 0x0c, 0x00, 0x38, 0x04, 0x3c, 0x44, 0x3c, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x44, 0x38, 0x04, 0x3c, 0x44, 0x3c, 0x00, 0x00, 0x00, }, + { 0x00, 0x28, 0x50, 0x38, 0x04, 0x3c, 0x44, 0x3c, 0x00, 0x00, 0x00, }, + { 0x00, 0x28, 0x00, 0x38, 0x04, 0x3c, 0x44, 0x3c, 0x00, 0x00, 0x00, }, + { 0x00, 0x10, 0x00, 0x38, 0x04, 0x3c, 0x44, 0x3c, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x2c, 0x52, 0x7c, 0x50, 0x2e, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x38, 0x44, 0x40, 0x44, 0x38, 0x08, 0x10, 0x00, 0x00, }, + { 0x00, 0x60, 0x00, 0x38, 0x44, 0x7c, 0x40, 0x3c, 0x00, 0x00, 0x00, }, + { 0x00, 0x0c, 0x00, 0x38, 0x44, 0x7c, 0x40, 0x3c, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x44, 0x38, 0x44, 0x7c, 0x40, 0x3c, 0x00, 0x00, 0x00, }, + { 0x00, 0x28, 0x00, 0x38, 0x44, 0x7c, 0x40, 0x3c, 0x00, 0x00, 0x00, }, + { 0x00, 0x30, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, + { 0x00, 0x18, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, + { 0x00, 0x10, 0x28, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, + { 0x00, 0x28, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, + { 0x00, 0x10, 0x08, 0x3c, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x28, 0x50, 0x78, 0x44, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, }, + { 0x00, 0x60, 0x00, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x0c, 0x00, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x38, 0x44, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x28, 0x50, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x28, 0x00, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x10, 0x00, 0x7c, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, }, + { 0x00, 0x00, 0x00, 0x38, 0x4c, 0x54, 0x64, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x60, 0x00, 0x44, 0x44, 0x44, 0x44, 0x3c, 0x00, 0x00, 0x00, }, + { 0x00, 0x0c, 0x00, 0x44, 0x44, 0x44, 0x44, 0x3c, 0x00, 0x00, 0x00, }, + { 0x00, 0x10, 0x28, 0x00, 0x44, 0x44, 0x44, 0x3c, 0x00, 0x00, 0x00, }, + { 0x00, 0x28, 0x00, 0x44, 0x44, 0x44, 0x44, 0x3c, 0x00, 0x00, 0x00, }, + { 0x00, 0x0c, 0x00, 0x44, 0x44, 0x44, 0x3c, 0x04, 0x78, 0x00, 0x00, }, + { 0x00, 0x30, 0x10, 0x18, 0x14, 0x18, 0x10, 0x38, 0x00, 0x00, 0x00, }, + { 0x00, 0x28, 0x00, 0x44, 0x44, 0x44, 0x3c, 0x04, 0x78, 0x00, 0x00, }, +}; diff --git a/cc-common.h b/cc-common.h new file mode 100644 index 0000000..7abd47d --- /dev/null +++ b/cc-common.h @@ -0,0 +1,25 @@ +#ifndef _CC_COMMON_H_ +#define _CC_COMMON_H_ + +#include + +typedef uint8_t GlyphBitmap[11]; + +struct rgba { uint8_t r, g, b, a; }; +union color { + struct rgba rgba; + uint32_t v; +}; + +struct palette { + const uint8_t count; + union color colors[] __attribute__((counted_by(count))); +}; + +#define LENGTHOF(...) (sizeof(__VA_ARGS__) / sizeof(*(__VA_ARGS__))) +#define PALETTE(...) { .count = LENGTHOF((union color[]){__VA_ARGS__}), .colors = {__VA_ARGS__} } + +const extern GlyphBitmap cc_font_atlas[256]; +const extern struct palette cc_default_palette, cc_default_gray_palette; + +#endif diff --git a/cc-pic.py b/cc-pic.py index f605430..e654379 100755 --- a/cc-pic.py +++ b/cc-pic.py @@ -33,22 +33,25 @@ class Converter: ] DEFAULT_PALETTE = [ - 240, 240, 240, - 242, 178, 51, - 229, 127, 216, - 153, 178, 242, - 222, 222, 108, - 127, 204, 25, - 242, 178, 204, - 76, 76, 76, - 153, 153, 153, - 76, 153, 178, - 178, 102, 229, - 51, 102, 204, - 127, 102, 76, - 87, 166, 78, - 204, 76, 76, - 17, 17, 17 + 0xf0, 0xf0, 0xf0, + 0xf2, 0xb2, 0x33, + 0xe5, 0x7f, 0xd8, + 0x99, 0xb2, 0xf2, + + 0xde, 0xde, 0x6c, + 0x7f, 0xcc, 0x19, + 0xf2, 0xb2, 0xcc, + 0x4c, 0x4c, 0x4c, + + 0x99, 0x99, 0x99, + 0x4c, 0x99, 0xb2, + 0xb2, 0x66, 0xe5, + 0x33, 0x66, 0xcc, + + 0x7f, 0x66, 0x4c, + 0x57, 0xa6, 0x4e, + 0xcc, 0x4c, 0x4c, + 0x11, 0x11, 0x11 ] DEFAULT_GRAYSCALE_PALETTE = [ @@ -399,7 +402,7 @@ def main(): raise ValueError(f"invalid palette identifier: {args.palette!r}") converter = Converter(canv, palette, dither=not args.nodither) - converter._img.save("/tmp/_ccpictmp.png") + # converter._img.save("/tmp/_ccpictmp.png") if args.textmode: with open(args.output_path, "w") as fp: converter.export(fp) diff --git a/ccpi.lua b/ccpi.lua index 849182c..a941271 100644 --- a/ccpi.lua +++ b/ccpi.lua @@ -64,9 +64,29 @@ decoders[1] = function(image, fp) return true end +decoders[128] = function(image, fp) + image.w = read_varint(fp) + image.h = read_varint(fp) + image.extras.n_frames = read_varint(fp) + read_palette_full(image.palette, fp) + local success, err = read_pixeldata_v0(image, fp) + if not success then return false, err end + image.extras.frames = {} + for i = 1, image.extras.n_frames - 1 do + local frame = { palette = {}, lines = {} } + frame.w = image.w + frame.h = image.h + frame.scale = image.scale + read_palette_full(frame.palette) + local success, err = read_pixeldata_v0(frame, fp) + if not success then return false, err end + end + return true +end + local function parse(fp) local res - local image = { w = 0, h = 0, scale = 1.0, palette = {}, lines = {} } + local image = { w = 0, h = 0, scale = 1.0, palette = {}, lines = {}, extras = {} } local magic = fp.read(4) if magic == "CCPI" then diff --git a/cpi2png.c b/cpi2png.c new file mode 100644 index 0000000..a321d82 --- /dev/null +++ b/cpi2png.c @@ -0,0 +1,108 @@ +// x-run: make test-cpi2png + +#include +#include +#include +#include +#include +#include +#include + +#include "cc-common.h" + +bool read_varint(FILE *fp, unsigned int *out); + +int main(int argc, char **argv) { + if (argc < 3) { + fprintf(stderr, "Usage: %s [input.cpi] [output.png]\n", argv[0]); + } + + FILE *fp_in = fopen(argv[1], "rb"); + assert(fp_in != NULL && "Failed to open input file"); + + unsigned char header[4]; + + unsigned char version = 0; + assert(fread(header, 1, 4, fp_in) == 4 && "Failed to read header: not enough bytes"); + if (0 == memcmp(header, "CCPI", 4)) { // Original CCPI (CPIv0) + version = 0; + } else if (0 == memcmp(header, "CPI", 3)) { // Newer CCPI (CPIvX) + version = header[3]; + } else { + assert(false && "Not a CPI/CCPI image: invalid header"); + } + + if (version & 0x80) { + fprintf(stderr, "Draft version: 0x%02x may not be supported properly! Here be dragons!\n", version); + } + + unsigned int width = 0, height = 0; + + if (version == 0) { + width = fgetc(fp_in); + height = fgetc(fp_in); + (void)fgetc(fp_in); // XXX: ignore scale + } else if (version == 1) { + assert(read_varint(fp_in, &width) && "Failed to read width varint"); + assert(read_varint(fp_in, &height) && "Failed to read height varint"); + } else { + assert(false && "Failed to read size: unsupported version"); + } + + union color *canvas = malloc(width * height * 6 * 9 * sizeof(union color)); + + + // XXX: may change in future when we introduce variable-size palettes + // though, it may never change, if I'm being honest. Why would I choose + // worse image quality with less colors when I can use all of them? + union color *colors = calloc(16, sizeof(union color)); + + // NOTE: our `union color` type is 4 bytes long, while palette stored in the + // file itself uses 3 bytes per color, so we can't just `fread` them at once, + // sadly. + for (int i = 0; i < 16; i++) { + colors[i].rgba.r = fgetc(fp_in); + colors[i].rgba.g = fgetc(fp_in); + colors[i].rgba.b = fgetc(fp_in); + colors[i].rgba.a = 0xff; + } + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + unsigned char sym = fgetc(fp_in); + unsigned char color = fgetc(fp_in); + union color background = colors[color & 0xF]; + union color foreground = colors[color >> 4]; + for (int oy = 0; oy < 9; oy++) { + for (int ox = 0; ox < 6; ox++) { + union color pix = ((0x80 >> (ox + 1)) & cc_font_atlas[sym][oy + 1]) ? foreground : background; + canvas[ox + (x + (y * 9 + oy) * width) * 6] = pix; + } + } + } + } + + stbi_write_png(argv[2], width * 6, height * 9, 4, canvas, 0); + + free(colors); + free(canvas); + fclose(fp_in); + return EXIT_SUCCESS; +} + +bool read_varint(FILE *fp, unsigned int *out) { + int position = 0; + + while (true) { + unsigned char curr = fgetc(fp); + *out |= (curr & 0x7F) << position; + + if ((curr & 0x80) == 0) break; + + position += 7; + + if (position >= 32) return false; + } + + return true; +} diff --git a/img2cpi.c b/img2cpi.c index 0c9f44a..992ce21 100644 --- a/img2cpi.c +++ b/img2cpi.c @@ -1,7 +1,8 @@ +// x-run: make img2cpi CC=clang // x-run: ~/scripts/runc.sh % -Wall -Wextra -lm --- ~/images/boykisser.png cpi-images/boykisser.cpi -#define STB_IMAGE_IMPLEMENTATION +#include +#include #include -#define STB_IMAGE_RESIZE_IMPLEMENTATION #include #include #include @@ -11,6 +12,7 @@ #include #include #include +#include "cc-common.h" #define MAX_COLOR_DIFFERENCE 768 #define K_MEANS_ITERATIONS 4 @@ -18,25 +20,10 @@ # define HAS_POPCNT 1 #endif -struct rgba { uint8_t r, g, b, a; }; -union color { - struct rgba rgba; - uint32_t v; -}; struct cc_char { unsigned char character; unsigned char bg, fg; }; -struct palette { - const uint8_t count; - union color colors[] __attribute__((counted_by(count))); -}; -#define LENGTHOF(...) (sizeof(__VA_ARGS__) / sizeof(*(__VA_ARGS__))) -#define PALETTE(...) { .count = LENGTHOF((union color[]){__VA_ARGS__}), .colors = {__VA_ARGS__} } -typedef char GlyphBitmap[11]; - -const extern GlyphBitmap font_atlas[256]; -const extern struct palette DEFAULT_PALETTE, DEFAULT_GRAY_PALETTE; struct arguments { int width, height; @@ -216,11 +203,11 @@ int main(int argc, char **argv) { } // TODO: load palette, maybe calculate it too? k-means? - const struct palette *palette = &DEFAULT_PALETTE; + const struct palette *palette = &cc_default_palette; switch (args.palette_type) { - case PALETTE_DEFAULT: palette = &DEFAULT_PALETTE; break; - case PALETTE_DEFAULT_GRAY: palette = &DEFAULT_GRAY_PALETTE; break; - case PALETTE_AUTO: palette = palette_k_means(src_image, &DEFAULT_PALETTE); break; + case PALETTE_DEFAULT: palette = &cc_default_palette; break; + case PALETTE_DEFAULT_GRAY: palette = &cc_default_gray_palette; break; + case PALETTE_AUTO: palette = palette_k_means(src_image, &cc_default_palette); break; case PALETTE_LIST: assert(0 && "Not implemented"); break; case PALETTE_PATH: assert(0 && "Not implemented"); break; default: assert(0 && "Unreachable"); @@ -680,7 +667,7 @@ void convert_8x11(const struct image_pal *img, struct cc_char *characters, bool const int sym = closest_chunk_color_symbol(&chunk_palette_diffs, color, precise); float difference = 0; for (int oy = 0; oy < 11; oy++) { - unsigned char sym_line = font_atlas[sym][oy]; + unsigned char sym_line = cc_font_atlas[sym][oy]; for (int ox = 0; ox < 8; ox++) { bool lit = sym_line & (0x80 >> ox); difference += chunk_palette_diffs[ox][oy][lit ? color >> 4 : color & 0xF]; @@ -819,7 +806,7 @@ void k_means_end(struct k_means_state *state) { struct palette *palette_k_means(const struct image *image, const struct palette *prototype) { if (!prototype) { - prototype = &DEFAULT_PALETTE; + prototype = &cc_default_palette; } struct palette *palette = custom_palette_from(prototype); @@ -872,12 +859,12 @@ float weighted_glyph_hamming_distance(const GlyphBitmap *lhs, const GlyphBitmap uint8_t closest_glyph_symbol_fast(const GlyphBitmap *target) { uint8_t best = 0x01; - int best_dist = glyph_hamming_distance(target, &font_atlas[best]); + int best_dist = glyph_hamming_distance(target, &cc_font_atlas[best]); for (int sym = 0x02; sym <= 0xFF; sym++) { if (sym == '\t' || sym == '\n' || sym == '\r' || sym == '\x0e') { continue; } - int dist = glyph_hamming_distance(target, &font_atlas[sym]); + int dist = glyph_hamming_distance(target, &cc_font_atlas[sym]); if (dist <= best_dist) { best_dist = dist; best = sym; @@ -888,12 +875,12 @@ uint8_t closest_glyph_symbol_fast(const GlyphBitmap *target) { uint8_t closest_glyph_symbol_precise(const GlyphBitmap *target, const typeof(float[11][8]) *weights) { uint8_t best = 0x01; - float best_dist = weighted_glyph_hamming_distance(target, &font_atlas[best], weights); + float best_dist = weighted_glyph_hamming_distance(target, &cc_font_atlas[best], weights); for (int sym = 0x02; sym <= 0xFF; sym++) { if (sym == '\t' || sym == '\n' || sym == '\r' || sym == '\x0e') { continue; } - float dist = weighted_glyph_hamming_distance(target, &font_atlas[sym], weights); + float dist = weighted_glyph_hamming_distance(target, &cc_font_atlas[sym], weights); if (dist <= best_dist) { best_dist = dist; best = sym; @@ -930,298 +917,3 @@ uint8_t closest_chunk_color_symbol(const typeof(float[8][11][0x10]) *chunk_palet return closest_glyph_symbol_fast(&glyph); } } - -const struct palette DEFAULT_PALETTE = PALETTE( - { { 0xf0, 0xf0, 0xf0, 0xff } }, - { { 0xf2, 0xb2, 0x33, 0xff } }, - { { 0xe5, 0x7f, 0xd8, 0xff } }, - { { 0x99, 0xb2, 0xf2, 0xff } }, - { { 0xde, 0xde, 0x6c, 0xff } }, - { { 0x7f, 0xcc, 0x19, 0xff } }, - { { 0xf2, 0xb2, 0xcc, 0xff } }, - { { 0x4c, 0x4c, 0x4c, 0xff } }, - { { 0x99, 0x99, 0x99, 0xff } }, - { { 0x4c, 0x99, 0xb2, 0xff } }, - { { 0xb2, 0x66, 0xe5, 0xff } }, - { { 0x33, 0x66, 0xcc, 0xff } }, - { { 0x7f, 0x66, 0x4c, 0xff } }, - { { 0x57, 0xa6, 0x4e, 0xff } }, - { { 0xcc, 0x4c, 0x4c, 0xff } }, - { { 0x11, 0x11, 0x11, 0xff } } -), DEFAULT_GRAY_PALETTE = PALETTE( - { { 0xf0, 0xf0, 0xf0, 0xff } }, - { { 0x9d, 0x9d, 0x9d, 0xff } }, - { { 0xbe, 0xbe, 0xbe, 0xff } }, - { { 0xbf, 0xbf, 0xbf, 0xff } }, - { { 0xb8, 0xb8, 0xb8, 0xff } }, - { { 0x76, 0x76, 0x76, 0xff } }, - { { 0xd0, 0xd0, 0xd0, 0xff } }, - { { 0x4c, 0x4c, 0x4c, 0xff } }, - { { 0x99, 0x99, 0x99, 0xff } }, - { { 0x87, 0x87, 0x87, 0xff } }, - { { 0xa9, 0xa9, 0xa9, 0xff } }, - { { 0x77, 0x77, 0x77, 0xff } }, - { { 0x65, 0x65, 0x65, 0xff } }, - { { 0x6e, 0x6e, 0x6e, 0xff } }, - { { 0x76, 0x76, 0x76, 0xff } }, - { { 0x11, 0x11, 0x11, 0xff } } -); - -const char font_atlas[256][11] = { - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x44, 0x6c, 0x44, 0x54, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x7c, 0x54, 0x7c, 0x44, 0x6c, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x28, 0x7c, 0x7c, 0x7c, 0x38, 0x10, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x10, 0x38, 0x7c, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x10, 0x38, 0x10, 0x7c, 0x7c, 0x10, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x10, 0x38, 0x7c, 0x7c, 0x10, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x18, 0x3c, 0x3c, 0x18, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x7e, 0x7e, 0x66, 0x42, 0x42, 0x66, 0x7e, 0x7e, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x1c, 0x0c, 0x34, 0x48, 0x48, 0x30, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x44, 0x44, 0x44, 0x38, 0x10, 0x38, 0x10, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x3c, 0x24, 0x3c, 0x20, 0x60, 0x60, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x3e, 0x22, 0x3e, 0x22, 0x66, 0x66, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x40, 0x70, 0x7c, 0x70, 0x40, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x04, 0x1c, 0x7c, 0x1c, 0x04, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x10, 0x38, 0x7c, 0x10, 0x10, 0x7c, 0x38, 0x10, 0x00, 0x00, }, - { 0x00, 0x24, 0x24, 0x24, 0x24, 0x24, 0x00, 0x24, 0x00, 0x00, 0x00, }, - { 0x00, 0x3c, 0x54, 0x54, 0x34, 0x14, 0x14, 0x14, 0x00, 0x00, 0x00, }, - { 0x00, 0x3c, 0x60, 0x58, 0x44, 0x34, 0x0c, 0x78, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x3c, 0x00, 0x00, 0x00, }, - { 0x00, 0x10, 0x38, 0x7c, 0x10, 0x7c, 0x38, 0x10, 0x7c, 0x00, 0x00, }, - { 0x00, 0x10, 0x38, 0x7c, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, - { 0x00, 0x10, 0x10, 0x10, 0x10, 0x7c, 0x38, 0x10, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x10, 0x18, 0x7c, 0x18, 0x10, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x10, 0x30, 0x7c, 0x30, 0x10, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x7c, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x24, 0x7e, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x10, 0x10, 0x38, 0x38, 0x7c, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x7c, 0x38, 0x38, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, }, - { 0x00, 0x14, 0x14, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x28, 0x28, 0x7c, 0x28, 0x7c, 0x28, 0x28, 0x00, 0x00, 0x00, }, - { 0x00, 0x10, 0x3c, 0x40, 0x38, 0x04, 0x78, 0x10, 0x00, 0x00, 0x00, }, - { 0x00, 0x44, 0x48, 0x08, 0x10, 0x20, 0x24, 0x44, 0x00, 0x00, 0x00, }, - { 0x00, 0x10, 0x28, 0x10, 0x34, 0x58, 0x48, 0x34, 0x00, 0x00, 0x00, }, - { 0x00, 0x08, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x0c, 0x10, 0x20, 0x20, 0x20, 0x10, 0x0c, 0x00, 0x00, 0x00, }, - { 0x00, 0x30, 0x08, 0x04, 0x04, 0x04, 0x08, 0x30, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x24, 0x18, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x10, 0x10, 0x7c, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, }, - { 0x00, 0x04, 0x08, 0x08, 0x10, 0x20, 0x20, 0x40, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x44, 0x4c, 0x54, 0x64, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x10, 0x30, 0x10, 0x10, 0x10, 0x10, 0x7c, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x44, 0x04, 0x18, 0x20, 0x44, 0x7c, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x44, 0x04, 0x18, 0x04, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x0c, 0x14, 0x24, 0x44, 0x7c, 0x04, 0x04, 0x00, 0x00, 0x00, }, - { 0x00, 0x7c, 0x40, 0x78, 0x04, 0x04, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x18, 0x20, 0x40, 0x78, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x7c, 0x44, 0x04, 0x08, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x44, 0x44, 0x38, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x44, 0x44, 0x3c, 0x04, 0x08, 0x30, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x10, 0x10, 0x00, 0x00, }, - { 0x00, 0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x20, 0x10, 0x08, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x44, 0x04, 0x08, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, }, - { 0x00, 0x3c, 0x42, 0x5a, 0x5a, 0x5e, 0x40, 0x3c, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x44, 0x7c, 0x44, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, }, - { 0x00, 0x78, 0x44, 0x78, 0x44, 0x44, 0x44, 0x78, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x44, 0x40, 0x40, 0x40, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x78, 0x44, 0x44, 0x44, 0x44, 0x44, 0x78, 0x00, 0x00, 0x00, }, - { 0x00, 0x7c, 0x40, 0x70, 0x40, 0x40, 0x40, 0x7c, 0x00, 0x00, 0x00, }, - { 0x00, 0x7c, 0x40, 0x70, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, }, - { 0x00, 0x3c, 0x40, 0x4c, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x44, 0x44, 0x7c, 0x44, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x44, 0x48, 0x70, 0x48, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, }, - { 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7c, 0x00, 0x00, 0x00, }, - { 0x00, 0x44, 0x6c, 0x54, 0x44, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, }, - { 0x00, 0x44, 0x64, 0x54, 0x4c, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x78, 0x44, 0x78, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x44, 0x44, 0x44, 0x44, 0x48, 0x34, 0x00, 0x00, 0x00, }, - { 0x00, 0x78, 0x44, 0x78, 0x44, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, }, - { 0x00, 0x3c, 0x40, 0x38, 0x04, 0x04, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x7c, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, - { 0x00, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x44, 0x44, 0x44, 0x44, 0x28, 0x28, 0x10, 0x00, 0x00, 0x00, }, - { 0x00, 0x44, 0x44, 0x44, 0x44, 0x54, 0x6c, 0x44, 0x00, 0x00, 0x00, }, - { 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, }, - { 0x00, 0x44, 0x28, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, - { 0x00, 0x7c, 0x04, 0x08, 0x10, 0x20, 0x40, 0x7c, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x20, 0x20, 0x20, 0x20, 0x20, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x40, 0x20, 0x20, 0x10, 0x08, 0x08, 0x04, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x08, 0x08, 0x08, 0x08, 0x08, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, }, - { 0x00, 0x10, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x38, 0x04, 0x3c, 0x44, 0x3c, 0x00, 0x00, 0x00, }, - { 0x00, 0x40, 0x40, 0x58, 0x64, 0x44, 0x44, 0x78, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x38, 0x44, 0x40, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x04, 0x04, 0x34, 0x4c, 0x44, 0x44, 0x3c, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x38, 0x44, 0x7c, 0x40, 0x3c, 0x00, 0x00, 0x00, }, - { 0x00, 0x0c, 0x10, 0x3c, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x3c, 0x44, 0x44, 0x3c, 0x04, 0x78, 0x00, 0x00, }, - { 0x00, 0x40, 0x40, 0x58, 0x64, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, }, - { 0x00, 0x10, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, - { 0x00, 0x04, 0x00, 0x04, 0x04, 0x04, 0x44, 0x44, 0x38, 0x00, 0x00, }, - { 0x00, 0x20, 0x20, 0x24, 0x28, 0x30, 0x28, 0x24, 0x00, 0x00, 0x00, }, - { 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x08, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x68, 0x54, 0x54, 0x44, 0x44, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x78, 0x44, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x58, 0x64, 0x44, 0x78, 0x40, 0x40, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x34, 0x4c, 0x44, 0x3c, 0x04, 0x04, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x58, 0x64, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x3c, 0x40, 0x38, 0x04, 0x78, 0x00, 0x00, 0x00, }, - { 0x00, 0x10, 0x10, 0x38, 0x10, 0x10, 0x10, 0x08, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x44, 0x44, 0x44, 0x44, 0x3c, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x44, 0x44, 0x44, 0x28, 0x10, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x44, 0x44, 0x54, 0x54, 0x3c, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x44, 0x44, 0x44, 0x3c, 0x04, 0x78, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x7c, 0x08, 0x10, 0x20, 0x7c, 0x00, 0x00, 0x00, }, - { 0x00, 0x0c, 0x10, 0x10, 0x20, 0x10, 0x10, 0x0c, 0x00, 0x00, 0x00, }, - { 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, - { 0x00, 0x30, 0x08, 0x08, 0x04, 0x08, 0x08, 0x30, 0x00, 0x00, 0x00, }, - { 0x00, 0x32, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x24, 0x48, 0x12, 0x24, 0x48, 0x12, 0x24, 0x48, 0x12, 0x00, }, - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0xfe, 0xfe, 0xfe, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, }, - { 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, }, - { 0x0f, 0x0f, 0x0f, 0x0f, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, }, - { 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, }, - { 0xf0, 0xf0, 0xf0, 0xf0, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, }, - { 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, }, - { 0xff, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, }, - { 0xf0, 0xf0, 0xf0, 0xf0, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, }, - { 0x0f, 0x0f, 0x0f, 0x0f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, }, - { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, }, - { 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, }, - { 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, }, - { 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, }, - { 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, }, - { 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, }, - { 0x0f, 0x0f, 0x0f, 0x0f, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, }, - { 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, }, - { 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x0f, 0xf0, 0xf0, 0xf0, 0xf0, }, - { 0xf0, 0xf0, 0xf0, 0xf0, 0x0f, 0x0f, 0x0f, 0xf0, 0xf0, 0xf0, 0xf0, }, - { 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0xf0, 0xf0, 0xf0, 0xf0, }, - { 0x7f, 0x7f, 0x7f, 0x7f, 0x0f, 0x0f, 0x0f, 0xf0, 0xf0, 0xf0, 0xf0, }, - { 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, }, - { 0xf0, 0xf0, 0xf0, 0xf0, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, }, - { 0x0f, 0x0f, 0x0f, 0x0f, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, }, - { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, }, - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x10, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x10, 0x38, 0x44, 0x40, 0x44, 0x38, 0x10, 0x00, 0x00, }, - { 0x00, 0x18, 0x24, 0x20, 0x78, 0x20, 0x20, 0x7c, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x44, 0x38, 0x44, 0x44, 0x44, 0x38, 0x44, 0x00, 0x00, }, - { 0x00, 0x44, 0x28, 0x7c, 0x10, 0x7c, 0x10, 0x10, 0x00, 0x00, 0x00, }, - { 0x00, 0x10, 0x10, 0x10, 0x00, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, - { 0x00, 0x3c, 0x60, 0x58, 0x44, 0x34, 0x0c, 0x78, 0x00, 0x00, 0x00, }, - { 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x3c, 0x4a, 0x52, 0x52, 0x4a, 0x3c, 0x00, 0x00, 0x00, }, - { 0x00, 0x30, 0x08, 0x38, 0x48, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x14, 0x28, 0x50, 0x28, 0x14, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x00, 0x7c, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x3c, 0x5a, 0x5a, 0x56, 0x42, 0x3c, 0x00, 0x00, 0x00, }, - { 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x30, 0x48, 0x48, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x10, 0x10, 0x7c, 0x10, 0x10, 0x00, 0x7c, 0x00, 0x00, }, - { 0x00, 0x40, 0x20, 0x60, 0x40, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x60, 0x20, 0x60, 0x20, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x20, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x44, 0x44, 0x44, 0x44, 0x7a, 0x40, 0x40, 0x00, }, - { 0x00, 0x3c, 0x54, 0x54, 0x34, 0x14, 0x14, 0x14, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x00, 0x00, }, - { 0x00, 0x20, 0x60, 0x20, 0x20, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x50, 0x28, 0x14, 0x28, 0x50, 0x00, 0x00, 0x00, }, - { 0x00, 0x44, 0x48, 0x08, 0x10, 0x2c, 0x2c, 0x44, 0x00, 0x00, 0x00, }, - { 0x00, 0x44, 0x48, 0x08, 0x10, 0x24, 0x28, 0x4c, 0x00, 0x00, 0x00, }, - { 0x00, 0x64, 0x28, 0x68, 0x10, 0x2c, 0x2c, 0x44, 0x00, 0x00, 0x00, }, - { 0x00, 0x10, 0x00, 0x10, 0x20, 0x40, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x60, 0x00, 0x38, 0x44, 0x7c, 0x44, 0x44, 0x00, 0x00, 0x00, }, - { 0x00, 0x0c, 0x00, 0x38, 0x44, 0x7c, 0x44, 0x44, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x44, 0x38, 0x44, 0x7c, 0x44, 0x44, 0x00, 0x00, 0x00, }, - { 0x00, 0x28, 0x50, 0x38, 0x44, 0x7c, 0x44, 0x44, 0x00, 0x00, 0x00, }, - { 0x00, 0x28, 0x00, 0x38, 0x44, 0x7c, 0x44, 0x44, 0x00, 0x00, 0x00, }, - { 0x00, 0x10, 0x00, 0x38, 0x44, 0x7c, 0x44, 0x44, 0x00, 0x00, 0x00, }, - { 0x00, 0x3c, 0x50, 0x50, 0x78, 0x50, 0x50, 0x5c, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x44, 0x40, 0x40, 0x44, 0x38, 0x08, 0x10, 0x00, 0x00, }, - { 0x00, 0x60, 0x00, 0x7c, 0x40, 0x78, 0x40, 0x7c, 0x00, 0x00, 0x00, }, - { 0x00, 0x0c, 0x00, 0x7c, 0x40, 0x78, 0x40, 0x7c, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x44, 0x7c, 0x40, 0x78, 0x40, 0x7c, 0x00, 0x00, 0x00, }, - { 0x00, 0x28, 0x00, 0x7c, 0x40, 0x78, 0x40, 0x7c, 0x00, 0x00, 0x00, }, - { 0x00, 0x30, 0x00, 0x38, 0x10, 0x10, 0x10, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x18, 0x00, 0x38, 0x10, 0x10, 0x10, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x10, 0x28, 0x38, 0x10, 0x10, 0x10, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x28, 0x00, 0x38, 0x10, 0x10, 0x10, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x78, 0x44, 0x44, 0x64, 0x44, 0x44, 0x78, 0x00, 0x00, 0x00, }, - { 0x00, 0x14, 0x28, 0x44, 0x64, 0x54, 0x4c, 0x44, 0x00, 0x00, 0x00, }, - { 0x00, 0x60, 0x38, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x0c, 0x38, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x44, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x28, 0x50, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x28, 0x38, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x44, 0x4c, 0x54, 0x64, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x60, 0x00, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x0c, 0x00, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x10, 0x28, 0x00, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x28, 0x00, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x0c, 0x00, 0x44, 0x28, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x10, 0x18, 0x14, 0x18, 0x10, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x78, 0x44, 0x58, 0x44, 0x44, 0x44, 0x58, 0x40, 0x00, 0x00, }, - { 0x00, 0x60, 0x00, 0x38, 0x04, 0x3c, 0x44, 0x3c, 0x00, 0x00, 0x00, }, - { 0x00, 0x0c, 0x00, 0x38, 0x04, 0x3c, 0x44, 0x3c, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x44, 0x38, 0x04, 0x3c, 0x44, 0x3c, 0x00, 0x00, 0x00, }, - { 0x00, 0x28, 0x50, 0x38, 0x04, 0x3c, 0x44, 0x3c, 0x00, 0x00, 0x00, }, - { 0x00, 0x28, 0x00, 0x38, 0x04, 0x3c, 0x44, 0x3c, 0x00, 0x00, 0x00, }, - { 0x00, 0x10, 0x00, 0x38, 0x04, 0x3c, 0x44, 0x3c, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x2c, 0x52, 0x7c, 0x50, 0x2e, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x38, 0x44, 0x40, 0x44, 0x38, 0x08, 0x10, 0x00, 0x00, }, - { 0x00, 0x60, 0x00, 0x38, 0x44, 0x7c, 0x40, 0x3c, 0x00, 0x00, 0x00, }, - { 0x00, 0x0c, 0x00, 0x38, 0x44, 0x7c, 0x40, 0x3c, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x44, 0x38, 0x44, 0x7c, 0x40, 0x3c, 0x00, 0x00, 0x00, }, - { 0x00, 0x28, 0x00, 0x38, 0x44, 0x7c, 0x40, 0x3c, 0x00, 0x00, 0x00, }, - { 0x00, 0x30, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, - { 0x00, 0x18, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, - { 0x00, 0x10, 0x28, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, - { 0x00, 0x28, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, }, - { 0x00, 0x10, 0x08, 0x3c, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x28, 0x50, 0x78, 0x44, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, }, - { 0x00, 0x60, 0x00, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x0c, 0x00, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x38, 0x44, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x28, 0x50, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x28, 0x00, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x10, 0x00, 0x7c, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, }, - { 0x00, 0x00, 0x00, 0x38, 0x4c, 0x54, 0x64, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x60, 0x00, 0x44, 0x44, 0x44, 0x44, 0x3c, 0x00, 0x00, 0x00, }, - { 0x00, 0x0c, 0x00, 0x44, 0x44, 0x44, 0x44, 0x3c, 0x00, 0x00, 0x00, }, - { 0x00, 0x10, 0x28, 0x00, 0x44, 0x44, 0x44, 0x3c, 0x00, 0x00, 0x00, }, - { 0x00, 0x28, 0x00, 0x44, 0x44, 0x44, 0x44, 0x3c, 0x00, 0x00, 0x00, }, - { 0x00, 0x0c, 0x00, 0x44, 0x44, 0x44, 0x3c, 0x04, 0x78, 0x00, 0x00, }, - { 0x00, 0x30, 0x10, 0x18, 0x14, 0x18, 0x10, 0x38, 0x00, 0x00, 0x00, }, - { 0x00, 0x28, 0x00, 0x44, 0x44, 0x44, 0x3c, 0x04, 0x78, 0x00, 0x00, }, -}; diff --git a/mess/cc-concat.lua b/mess/cc-concat.lua new file mode 100644 index 0000000..2340719 --- /dev/null +++ b/mess/cc-concat.lua @@ -0,0 +1,82 @@ +local args = { ... } +local ccpi = require("ccpi") + +local bit = { + band = function(a, b) return a & b end, + bor = function(a, b) return a | b end, + blshift = function(a, b) return a << b end, + brshift = function(a, b) return a >> b end, +} + +local function write_varint(fp, value) + value = bit.band(value, 0xFFFFFFFF) + mask = 0xFFFFFF80 + while true do + if bit.band(value, mask) == 0 then + fp:write(string.char(bit.band(value, 0xff))) + return + end + + fp:write(string.char(bit.bor(0x80, bit.band(value, 0x7f)))) + value = bit.brshift(value, 7) + end +end + +local function write_palette(fp, pal) + for i = 1, 16 do + fp:write(string.char( + bit.brshift(pal[i], 16), + bit.band(bit.brshift(pal[i], 8), 0xff), + bit.band(pal[i], 0xff) + )) + end +end + +local function write_pixeldata_v0(img, fp) + for y = 1, img.h do + for x = 1, img.w do + fp:write(img.lines[y].s:sub(x, x)) + local bg = tonumber(img.lines[y].bg:sub(x, x), 16) + local fg = tonumber(img.lines[y].fg:sub(x, x), 16) + fp:write(string.char(bit.bor(bg, bit.blshift(fg, 4)))) + end + end +end + +local fp_out = io.open(table.remove(args, 1), "wb") +fp_out:write("CPI" .. string.char(0x80)) + +local fp = io.open(table.remove(args, 1), "rb") +print(fp, fp.close) +local img, err = ccpi.parse({ + read = function(sz) return fp:read(sz) end, +}) +fp:close() +if err ~= nil then + printError(err) + return +end + +write_varint(fp_out, img.w) +write_varint(fp_out, img.h) +write_varint(fp_out, #args) + +write_palette(fp_out, img.palette) +write_pixeldata_v0(img, fp_out) + +for i, arg in ipairs(args) do + print(arg) + local fp = io.open(arg, "rb") + img, err = ccpi.parse({ + read = function(sz) return fp:read(sz) end, + }) + fp:close() + if err ~= nil then + printError(err) + return + end + write_palette(fp_out, img.palette) + write_pixeldata_v0(img, fp_out) +end + +fp_out:close() diff --git a/mess/libcloudcatcher.py b/mess/libcloudcatcher.py new file mode 100644 index 0000000..c460cba --- /dev/null +++ b/mess/libcloudcatcher.py @@ -0,0 +1,65 @@ + +from typing import Any, Literal +from websockets import connect + +HexDigit = Literal[ + "0", "1", "2", "3", "4", "5", "6", "7", + "8", "9", "a", "b", "c", "d", "e", "f" +] + +class Packet: + packet_id: int + side: Literal["client", "server"] + + def __init_subclass__(cls, packet_id: int, side: Literal["client", "server"]) -> None: + cls.packet_id = packet_id + cls.side = side + +class PacketServerPing(Packet, packet_id=2, side="server"): + pass + +class PacketClientPong(Packet, packet_id=2, side="client"): + pass + +class PacketServerCapabilities(Packet, packet_id=0, side="server"): + clients: int + capabilities: list[str] + +class PacketServerComputer(Packet, packet_id=18, side="server"): + id: int + label: str + +class PacketServerScreen(Packet, packet_id=16, side="server"): + cursorBlink: bool + width: int + height: int + cursorX: int + cursorY: int + curFore: HexDigit + curBack: HexDigit + text: list[str] + back: list[str] + fore: list[str] + palette: list[int] + +class CCEvent: + name: str + args: list[Any] + +class PacketClientEvent(Packet, packet_id=17, side="client"): + events: list[CCEvent] + +class CCAction: + action: int + + def __init_subclass__(cls, action: int) -> None: + cls.action = action + +class CCActionFile(CCAction, action=0): + file: str + contents: str + checksum: int + +class PacketServerFile(Packet, packet_id=34, side="server"): + actions: list[CCAction] + diff --git a/mess/package_video.py b/mess/package_video.py new file mode 100644 index 0000000..6053237 --- /dev/null +++ b/mess/package_video.py @@ -0,0 +1,150 @@ +# x-run: python3 % ~/downloads/moZtoMP7HAA.mp4 /tmp/video.cani +from typing import Literal +from dataclasses import dataclass +from struct import pack +from sys import argv +from subprocess import Popen, PIPE, run +from tqdm import tqdm +from tempfile import TemporaryDirectory +from glob import glob +from PIL import Image +from functools import lru_cache + + +PIX_BITS = [[1, 2], [4, 8], [16, 0]] + + +@dataclass +class VideoMetadata: + framerate: Literal[20, 10, 5] = 10 + audio_channels: Literal[1, 2] = 2 + sample_rate: Literal[12000, 24000, 48000] = 48000 + screen_width: int = 164 + screen_height: int = 81 + + @property + def audio_samples_per_frame(self) -> int: + return self.sample_rate // self.framerate + + def serialize(self) -> bytes: + return bytes([ self.framerate, self.audio_channels ]) \ + + pack(" bytes: + return bytes.join(b"", self.audio) \ + + bytes.join(b"", self.video) \ + + bytes.join(b"", [ bytes.fromhex("%06x" % color) for color in self.palette ]) + +@lru_cache +def _brightness(palette: tuple, i: int) -> float: + r, g, b = palette[i * 3 : (i + 1) * 3] + return (r + g + b) / 768 + +@lru_cache +def _distance(palette: tuple, a: int, b: int) -> float: + r1, g1, b1 = palette[a * 3 : (a + 1) * 3] + r2, g2, b2 = palette[b * 3 : (b + 1) * 3] + rd, gd, bd = r1 - r2, g1 - g2, b1 - b2 + return (rd * rd + gd * gd + bd * bd) / 1966608 + +@lru_cache +def _get_colors(imgdata, palette: tuple, x: int, y: int) -> tuple[int, int]: + brightest_i, brightest_l = 0, 0 + darkest_i, darkest_l = 0, 768 + for oy, line in enumerate(PIX_BITS): + for ox in range(len(line)): + pix = imgdata[x + ox, y + oy] + assert pix < 16, f"{pix} is too big at {x+ox}:{y+oy}" + brightness = _brightness(palette, pix) + if brightness > brightest_l: + brightest_l, brightest_i = brightness, pix + if brightness < darkest_l: + darkest_l, darkest_i = brightness, pix + return darkest_i, brightest_i + +@lru_cache() +def _is_darker(palette: tuple, bg: int, fg: int, c: int) -> bool: + return _distance(palette, bg, c) < _distance(palette, fg, c) + +def _get_block(imgdata, palette: tuple, x: int, y: int) -> tuple[int, int, int]: + dark_i, bri_i = _get_colors(imgdata, palette, x, y) + assert dark_i < 16, f"{dark_i} is too big" + assert bri_i < 16, f"{bri_i} is too big" + out: int = 0 + for oy, line in enumerate(PIX_BITS): + for ox, bit in enumerate(line): + if not _is_darker( + palette, dark_i, bri_i, imgdata[x + ox, y + oy] + ): + out |= bit + # bottom right pixel fix? + if not _is_darker(palette, dark_i, bri_i, imgdata[x + 1, y + 2]): + out ^= 31 + dark_i, bri_i = bri_i, dark_i + return out, dark_i, bri_i + +metadata = VideoMetadata( + framerate=20, + audio_channels=2, + sample_rate=24000, + screen_width=164, + screen_height=81 +) + +input_video = argv[1] + +with TemporaryDirectory() as tmpdir: + run([ + "ffmpeg", + "-i", input_video, + "-f", "s8", + "-ac", str(metadata.audio_channels), + "-ar", str(metadata.sample_rate), + f"{tmpdir}/audio.s8" + ]) + + run([ + "ffmpeg", + "-i", input_video, + "-an", + "-r", str(metadata.framerate), + "-vf", f"scale={metadata.screen_width * 2}:{metadata.screen_height * 3}", + f"{tmpdir}/video%06d.jpg" + ]) + + with open(argv[2], "w") as fp_out, open(f"{tmpdir}/audio.s8", "rb") as fp_audio: + print(metadata.serialize().hex(), file=fp_out) + for i, frame_path in tqdm(enumerate(glob(f"{tmpdir}/video*.jpg"))): + with Image.open(frame_path) as img_in: + img_in = img_in.convert("P", palette=Image.Palette.ADAPTIVE, colors=16) + img_data = img_in.load() + img_palette = tuple(img_in.getpalette()) # type: ignore + + audio_samples = fp_audio.read(metadata.audio_samples_per_frame * metadata.audio_channels) + + frame = VideoFrame( + [ + audio_samples[i::metadata.audio_channels] + for i in range(metadata.audio_channels) + ], + [], + [ + (r << 16) | (g << 8) | b + for r, g, b + in zip(img_palette[0::3], img_palette[1::3], img_palette[2::3]) # type: ignore + ]) + + for y in range(0, img_in.height - 2, 3): + line = bytearray() + for x in range(0, img_in.width - 1, 2): + ch, bg, fg = _get_block(img_data, img_palette, x, y) + line.extend([(ch + 0x80) & 0xFF, fg << 4 | bg]) + frame.video.append(line) + + print(frame.serialize().hex(), file=fp_out) diff --git a/mess/pipez.lua b/mess/pipez.lua new file mode 100644 index 0000000..a36320c --- /dev/null +++ b/mess/pipez.lua @@ -0,0 +1,36 @@ + +local config = { + { filter = ".*charged.*", from = "ae2:charger", to = "create:basin" }, + { filter = "!.*charged.*", from = "create:basin", to = "ae2:charger", limit = 1 } +} + +local function is_matching(pattern, value) + if pattern:sub(1, 1) == "!" then + return not is_matching(pattern:sub(2)) + end + return value:match(pattern) ~= nil +end + + +local items = {} + +parallel.waitForAll(function() -- Inventory listener + while true do + local new_items = {} + peripheral.find("inventory", function(inv) + local inv_items = peripheral.call(inv, "list") + for slot, item in pairs(inv_items) do + new_items[string.format("%s#%d", inv, slot)] = item + end + end) + items = new_items + os.sleep(0) + end +end, function() -- Executor + while true do + for i, rule in ipairs(config) do + + end + os.sleep(0) + end +end) diff --git a/mess/ramfsd.lua b/mess/ramfsd.lua new file mode 100644 index 0000000..50ff6fb --- /dev/null +++ b/mess/ramfsd.lua @@ -0,0 +1,21 @@ + +peripheral.find("modem", function(name) + rednet.open(name) + print("Opened modem " .. name .. " for rednet connections") +end) + +print("Hostname: " .. os.getComputerLabel()) +print("ID: " .. os.getComputerID()) +rednet.host("ramfs", os.getComputerLabel()) + +parallel.waitForAll(function() + while true do + local ev = { os.pullEvent() } + if ev[1] == "ramfs:shutdown" then + break + end + print(table.unpack(ev)) + end +end, function() -- Shutdown routine + os.pullEvent("ramfs:shutdown") +end) diff --git a/mess/restock.json b/mess/restock.json new file mode 100644 index 0000000..69dfba1 --- /dev/null +++ b/mess/restock.json @@ -0,0 +1,84 @@ +{ + "junk": [ + "metalbarrels:gold_tile_4" + ], + "storage": [ + "create:item_vault_2", + "create:item_vault_1" + ], + "pull": [ + "minecraft:barrel_1", + "NOT IMPLEMENTED" + ], + "push": [ + { "name": "*_nugget", "to": "metalbarrels:gold_tile_8" }, + { "name": "*_ingot", "to": "metalbarrels:gold_tile_8" }, + { "name": "minecraft:*coal", "to": "metalbarrels:gold_tile_9" }, + { "name": "NOT IMPLEMENTED", "to": "NOT IMPLEMENTED" } + ], + "stock": { + "metalbarrels:gold_tile_13#1": { "name": "minecraft:spruce_log", "count": 64 }, + "metalbarrels:gold_tile_13#2": { "name": "minecraft:spruce_log", "count": 64 }, + "metalbarrels:gold_tile_13#3": { "name": "minecraft:spruce_planks", "count": 64 }, + "metalbarrels:gold_tile_13#10": { "name": "minecraft:oak_log", "count": 64 }, + "metalbarrels:gold_tile_13#11": { "name": "minecraft:oak_log", "count": 64 }, + "metalbarrels:gold_tile_13#12": { "name": "minecraft:oak_planks", "count": 64 }, + "metalbarrels:gold_tile_13#19": { "name": "minecraft:dark_oak_log", "count": 64 }, + "metalbarrels:gold_tile_13#20": { "name": "minecraft:dark_oak_log", "count": 64 }, + "metalbarrels:gold_tile_13#21": { "name": "minecraft:dark_oak_planks", "count": 64 }, + "metalbarrels:gold_tile_13#4": { "name": "minecraft:cobblestone", "count": 64 }, + "metalbarrels:gold_tile_13#5": { "name": "minecraft:cobblestone", "count": 64 }, + "metalbarrels:gold_tile_13#6": { "name": "minecraft:stone", "count": 64 }, + "metalbarrels:gold_tile_13#13": { "name": "minecraft:cobbled_deepslate", "count": 64 }, + "metalbarrels:gold_tile_13#14": { "name": "minecraft:cobbled_deepslate", "count": 64 }, + "metalbarrels:gold_tile_13#15": { "name": "minecraft:deepslate", "count": 64 }, + "metalbarrels:gold_tile_13#7": { "name": "minecraft:dirt", "count": 64 }, + "metalbarrels:gold_tile_13#16": { "name": "minecraft:sand", "count": 64 }, + "metalbarrels:gold_tile_13#25": { "name": "minecraft:gravel", "count": 64 }, + "metalbarrels:gold_tile_13#8": { "name": "minecraft:netherrack", "count": 64 }, + "metalbarrels:gold_tile_13#17": { "name": "minecraft:soul_sand", "count": 64 }, + "metalbarrels:gold_tile_13#22": { "name": "minecraft:andesite", "count": 64 }, + "metalbarrels:gold_tile_13#23": { "name": "minecraft:diorite", "count": 64 }, + "metalbarrels:gold_tile_13#24": { "name": "minecraft:granite", "count": 64 }, + "metalbarrels:gold_tile_13#31": { "name": "minecraft:tuff", "count": 64 }, + "metalbarrels:gold_tile_13#32": { "name": "expcaves:sediment_stone", "count": 64 }, + "metalbarrels:gold_tile_13#33": { "name": "expcaves:lavastone", "count": 64 }, + "metalbarrels:gold_tile_13#34": { "name": "expcaves:dirtstone", "count": 64 }, + "metalbarrels:gold_tile_13#42": { "name": "minecraft:calcite", "count": 64 }, + "metalbarrels:gold_tile_13#43": { "name": "minecraft:clay_ball", "count": 64 }, + "metalbarrels:gold_tile_13#40": { "name": "create:limestone", "count": 64 }, + "metalbarrels:gold_tile_13#49": { "name": "minecraft:stone_bricks", "count": 64 }, + "metalbarrels:gold_tile_13#50": { "name": "minecraft:cracked_stone_bricks", "count": 64 }, + "metalbarrels:gold_tile_13#51": { "name": "minecraft:moss_block", "count": 64 }, + "metalbarrels:gold_tile_13#41": { "name": "forbidden_arcanus:darkstone", "count": 64 }, + "metalbarrels:gold_tile_13#73": { "name": "minecraft:apple", "count": 64 }, + "metalbarrels:gold_tile_13#74": { "name": "minecraft:kelp", "count": 64 }, + "metalbarrels:gold_tile_13#75": { "name": "minecraft:oak_sapling", "count": 64 }, + "metalbarrels:gold_tile_13#76": { "name": "minecraft:spruce_sapling", "count": 64 }, + "metalbarrels:gold_tile_13#77": { "name": "minecraft:dark_oak_sapling", "count": 64 }, + "metalbarrels:gold_tile_13#81": { "name": "minecraft:stick", "count": 64 }, + "metalbarrels:gold_tile_13#26": { "name": "minecraft:nether_bricks", "count": 64 }, + "metalbarrels:gold_tile_13#35": { "name": "minecraft:magma_block", "count": 64 }, + + "metalbarrels:gold_tile_6#1": { "name": "kubejs:kinetic_mechanism", "count": 64 }, + "metalbarrels:gold_tile_6#2": { "name": "kubejs:kinetic_mechanism", "count": 64 }, + "metalbarrels:gold_tile_6#10": { "name": "create:andesite_alloy", "count": 64 }, + "metalbarrels:gold_tile_6#11": { "name": "create:andesite_alloy", "count": 64 }, + "metalbarrels:gold_tile_6#12": { "name": "thermal:cured_rubber", "count": 64 }, + "metalbarrels:gold_tile_6#19": { "name": "thermal:silver_coin", "count": 64 }, + "metalbarrels:gold_tile_6#20": { "name": "thermal:silver_coin", "count": 64 }, + "metalbarrels:gold_tile_6#21": { "name": "thermal:silver_coin", "count": 64 }, + + "metalbarrels:gold_tile_7#1": { "name": "minecraft:lapis_lazuli", "count": 64 }, + "metalbarrels:gold_tile_7#2": { "name": "minecraft:redstone", "count": 64 }, + "metalbarrels:gold_tile_7#3": { "name": "minecraft:coal", "count": 15 }, + "metalbarrels:gold_tile_7#4": { "name": "thermal:apatite", "count": 64 }, + "metalbarrels:gold_tile_7#5": { "name": "thermal:sulfur", "count": 64 }, + "metalbarrels:gold_tile_7#6": { "name": "thermal:niter", "count": 64 }, + "metalbarrels:gold_tile_7#7": { "name": "thermal:cinnabar", "count": 64 }, + "metalbarrels:gold_tile_7#10": { "name": "ae2:certus_quartz_dust", "count": 64 }, + "metalbarrels:gold_tile_7#11": { "name": "ae2:certus_crystal_seed", "count": 64 }, + "metalbarrels:gold_tile_7#12": { "name": "forbidden_arcanus:xpetrified_orb", "count": 16 } + } +} + diff --git a/mess/restock.lua b/mess/restock.lua new file mode 100644 index 0000000..a7f8d2f --- /dev/null +++ b/mess/restock.lua @@ -0,0 +1,434 @@ + +local function draw_bar(y, c1, c2, p, fmt, ...) + local str = string.format(fmt, ...) + local tw = term.getSize() + local w1 = math.ceil(p * tw) + local w2 = tw - w1 + + local old_bg = term.getBackgroundColor() + term.setCursorPos(1, y) + term.setBackgroundColor(c1) + term.write(str:sub(1, w1)) + local rem = w1 - #str + if rem > 0 then + term.write(string.rep(" ", rem)) + end + + term.setBackgroundColor(c2) + term.write(str:sub(w1 + 1, w1 + w2)) + + rem = math.min(tw - #str, w2) + if rem > 0 then + term.write(string.rep(" ", rem)) + end + + term.setBackgroundColor(old_bg) +end + +function table.deepcopy(obj) + if type(obj) ~= 'table' then return obj end + local res = {} + for k, v in pairs(obj) do res[table.deepcopy(k)] = table.deepcopy(v) end + return res +end + +function spairs(t, order) + -- collect the tbl_keys + local tbl_keys = {} + for k in pairs(t) do tbl_keys[#tbl_keys+1] = k end + + -- if order function given, sort by it by passing the table and tbl_keys a, b, + -- otherwise just sort the tbl_keys + if order then + table.sort(tbl_keys, function(a,b) return order(t, a, b) end) + else + table.sort(tbl_keys) + end + + -- return the iterator function + local i = 0 + return function() + i = i + 1 + if tbl_keys[i] then + return tbl_keys[i], t[tbl_keys[i]] + end + end +end + +function string.split(inputstr, sep) + sep = sep or "%s" + local t = {} + for str in string.gmatch(inputstr, "([^"..sep.."]+)") do + table.insert(t, str) + end + return table.unpack(t) +end + +function map(tbl, fn) + local out = {} + for k, v in pairs(tbl) do + out[k] = fn(k, v, tbl) + end + return out +end + +function filter(tbl, fil) + local out = {} + for k, v in pairs(tbl) do + if fil(k, v, tbl) then + out[k] = v + end + end + return out +end + +function pop(tbl) + local out = { k = nil, v = nil } + for k, v in pairs(tbl) do + out.k = k + out.v = v + table.remove(tbl, k) + break + end + return out.k, out.v +end + +function groupby(tbl, fn) + local out = {} + for k, v in pairs(tbl) do + local group = fn(k, v, tbl) + out[group] = out[group] or {} + table.insert(out[group], { k = k, v = v }) + end + return out +end + +function reduce(tbl, operator, initial) + local accumulator = initial + for k, v in pairs(tbl) do + accumulator = operator(k, v, accumulator, tbl) + end + return accumulator +end + +function pipe(input) + local function chain(fn) + return function(self, ...) + fn(self, ...) + return self + end + end + return { + _value = table.deepcopy(input), + groupby = chain(function(self, fn) self._value = groupby(self._value, fn) end), + filter = chain(function(self, fn) self._value = filter(self._value, fn) end), + map = chain(function(self, fn) self._value = map(self._value, fn) end), + reduce = chain(function(self, operator, initial) self._value = reduce(self._value, operator, initial) end), + sort = chain(function(self, key) + local out = {} + for k, v in spairs(self._value, key) do + table.insert(out, { k = k, v = v }) + end + self._value = out + end), + get = function(self) return self._value end + } +end + +local keyboard = { + "qwertyuiop", + "asdfghjkl\x1b", + "zxcvbnm \xd7", +} + +local config = { stock = {}, trash = {}, take = {}, storage = {} } +local item_state = { stock = {}, storage = {}, junk = {} } +local storage_sizes = { stock = {}, storage = {}, junk = {} } +local sort_mode, sort_inverse = "count", false +local search_open, search_query = false, "" + +local mon = peripheral.find("monitor") +mon.setTextScale(0.5) +mon.clear() +mon.setPaletteColor(colors.blue, 0x131326) -- odd lines +mon.setPaletteColor(colors.purple, 0x261326) -- even lines +mon.setPaletteColor(colors.magenta, 0xaa55ff) -- search query +mon.setPaletteColor(colors.cyan, 0x55aaff) -- keyboard +mon.setPaletteColor(colors.brown, 0x262626) -- keyboard bg + +parallel.waitForAll(function() + while true do + local fp = assert(io.open("/restock.json", "r")) + config = textutils.unserializeJSON(fp:read("a")) + fp:close() + os.sleep(1) + end +end, function() + while true do + local new_state = {} + local new_sizes = {} + map(config.storage, function(i, inv) + new_sizes[inv] = peripheral.call(inv, "size") + for slot, item in pairs(peripheral.call(inv, "list")) do + new_state[string.format("%s#%d", inv, slot)] = item + end + end) + item_state.storage = new_state + storage_sizes.storage = new_sizes + os.sleep(0) + end +end, function() + while true do + local new_state = {} + local new_sizes = {} + map( + groupby(config.stock, function(location) + local storage = string.split(location, "#") + return storage + end + ), function(inv) + new_sizes[inv] = peripheral.call(inv, "size") + for slot, item in pairs(peripheral.call(inv, "list")) do + new_state[string.format("%s#%d", inv, slot)] = item + end + end) + item_state.stock = new_state + storage_sizes.stock = new_sizes + os.sleep(0) + end +end, function() -- IMPORT + while true do + map( + groupby(config.stock, function(location) return ({ string.split(location, "#") })[1] end), + function(stock_inv) + local size = storage_sizes.stock[stock_inv] or 0 + for i = 1, size do + local slot = string.format("%s#%d", stock_inv, i) + local slot_stocked, slot_expected = item_state.stock[slot], config.stock[slot] + if (slot_stocked and slot_expected and slot_stocked.name ~= slot_expected.name) or (slot_stocked ~= nil and slot_expected == nil) then + for _, output in ipairs(config.storage) do + if peripheral.call(stock_inv, "pushItems", output, i) ~= 0 then + print("MOVE", slot, slot_stocked.name) + break + end + end + end + end + end) + + os.sleep(0) + end +end, function() -- STOCK + while true do + local new_junk = {} + local item_locations = groupby(item_state.storage, function(location, item) + new_junk[item.name] = (new_junk[item.name] or 0) + item.count + return item.name + end) + + local tw, th = term.getSize() + map(config.stock, function(destination, item) + local dst_container, dst_slot = string.split(destination, "#") + new_junk[item.name] = nil + if item_locations[item.name] == nil then return end + local dst_item = item_state.stock[destination] + local dst_count = dst_item and dst_item.count or 0 + if dst_count >= item.count then return end + local missing = item.count - dst_count + + local _, take_from = pop(filter(item_locations[item.name], function(_, src_item) + local src_container, src_slot = string.split(src_item.k, "#") + return src_container ~= dst_container + end)) + + if not take_from then return end + local src_container, src_slot = string.split(take_from.k, "#") + local t = os.clock() + local out = peripheral.call(dst_container, "pullItems", src_container, tonumber(src_slot), missing, tonumber(dst_slot)) + print(string.format("PUT %s*%d/%d to %s", item.name, out, missing, destination)) + -- print(string.format("%s -> %s (%s*%d) took %.2f", take_from.k, destination, item.name, item.count, os.clock() - t)) + end) + item_state.junk = new_junk + os.sleep(0) + end +end, function() -- JUNK + while true do + local y = 2 + for name, count in pairs(item_state.junk) do + map(filter(item_state.storage, function(location, item) + return item.name == name + end), function(location, item) + local junk_container, junk_slot = string.split(location, "#") + for _, junk_output in ipairs(config.junk) do + if peripheral.call(junk_container, "pushItems", junk_output, tonumber(junk_slot)) ~= 0 then + print("JUNK OUT", item.name, "->", junk_container) + break + end + end + end) + y = y + 1 + end + os.sleep(0) + end +end, function() -- USER INPUT + while true do + local ev = { os.pullEvent() } + if ev[1] == "char" and search_open then + search_query = search_query .. ev[2] + mon.setBackgroundColor(colors.gray) + mon.setTextColor(colors.magenta) + mon.setCursorPos(tw - 14, th - 20) + mon.write("> " .. search_query) + for i, line in ipairs(keyboard) do + mon.setCursorPos(tw - 14, th - 20 + i) + mon.setBackgroundColor(colors.brown) + mon.setTextColor(colors.cyan) + mon.write(line) + end + elseif ev[1] == "key" then + if ev[2] == keys.enter then + search_open = not search_open + elseif ev[2] == keys.backspace and search_open then + search_query = search_query:sub(1, #search_query - 1) + mon.setBackgroundColor(colors.gray) + mon.setTextColor(colors.magenta) + mon.setCursorPos(tw - 14, th - 20) + mon.write("> " .. search_query) + for i, line in ipairs(keyboard) do + mon.setCursorPos(tw - 14, th - 20 + i) + mon.setBackgroundColor(colors.brown) + mon.setTextColor(colors.cyan) + mon.write(line) + end + end + elseif ev[1] == "monitor_touch" then + local tw, th = mon.getSize() + local _, _, x, y = table.unpack(ev) + if y == 3 or y == th then + local new_mode + if x >= 1 and x <= 8 then new_mode = "count" + elseif x >= 10 and x <= (tw - 1) then new_mode = "name" + elseif x == tw then + search_open = not search_open + end + + if new_mode ~= nil and new_mode == sort_mode then + sort_inverse = not sort_inverse + elseif new_mode ~= nil then + sort_mode = new_mode + sort_inverse = false + end + elseif search_open and x >= (tw - 14) and x < (tw - 4) and y > (th - 20) and y <= (th - 17) then + mon.setBackgroundColor(colors.gray) + mon.setTextColor(colors.magenta) + mon.setCursorPos(tw - 14, th - 20) + mon.write("> " .. search_query) + for i, line in ipairs(keyboard) do + mon.setCursorPos(tw - 14, th - 20 + i) + mon.setBackgroundColor(colors.brown) + mon.setTextColor(colors.cyan) + mon.write(line) + end + + local char = keyboard[20 - th + y]:sub(15 - tw + x, 15 - tw + x) + if char >= "a" and char <= "z" then + os.queueEvent("key", keys[char], false) + os.queueEvent("char", char) + os.queueEvent("key_up", keys[char]) + else + -- 27 backspace (ev=259) + -- 215 enter (ev=257) + local code = nil + + if char == "\x1b" then code = keys.backspace + elseif char == "\xd7" then code = keys.enter + end + + if code then + os.queueEvent("key", code, false) + os.queueEvent("key_up", code) + end + end + end + end + end +end, function() -- STATUS + + while true do + local back_term = term.redirect(mon) + local tw, th = term.getSize() + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + + local usage = pipe(config.storage) + :map(function(_, storage) + local used, total = 0, storage_sizes.storage[storage] or 0 + for slot = 1, total do + if item_state.storage[string.format("%s#%d", storage, slot)] then + used = used + 1 + end + end + return { used = used, total = total } + end) + :reduce(function(_, info, acc) + return { used = info.used + acc.used, total = info.total + acc.total } + end, { used = 0, total = 0 }):get() + + draw_bar(1, colors.lightGray, colors.gray, usage.used / usage.total, "Storage utilization: %7.3f%%", 100 * usage.used / usage.total) + + term.setCursorPos(1, 3) + term.setBackgroundColor(colors.gray) + term.clearLine() + local infoline = string.format("Count %s Name %s", + (sort_mode == "count" and (sort_inverse and "\x1e" or "\x1f") or " "), + (sort_mode == "name" and (sort_inverse and "\x1e" or "\x1f") or " ")) + local fg = sort_mode == "count" and "55555551f88888881" or "88888881f55555551" + term.blit(infoline, fg, "77777777777777777") + term.setCursorPos(tw, 3) + term.write("\x0c") + term.setCursorPos(1, th) + term.clearLine() + term.blit(infoline, fg, "77777777777777777") + term.setCursorPos(tw, th) + term.write("\x0c") + + + pipe(item_state.storage) + :groupby(function(slot, item) return item.name end) + :filter(function(name, slots) + if not search_open then return true end + return string.match(name, search_query) + end) + :map(function(name, slots) return reduce(slots, function(_, slot, acc) return slot.v.count + acc end, 0) end) + :sort(function(t, a, b) + local swap = false + if sort_mode == "count" then swap = t[a] > t[b] end + if sort_mode == "name" then swap = a > b end + return swap ~= sort_inverse + end) + :map(function(i, kv) + if i >= (th - 4) then return end + term.setCursorPos(1, 3 + i) + term.setBackgroundColor((i % 2) == 0 and colors.blue or colors.purple) + term.clearLine() + term.write(string.format("%8d %s", kv.v, kv.k)) + end) + + if search_open then + mon.setBackgroundColor(colors.gray) + mon.setTextColor(colors.magenta) + term.setCursorPos(tw - 14, th - 20) + term.write("> " .. search_query) + for i, line in ipairs(keyboard) do + term.setCursorPos(tw - 14, th - 20 + i) + term.setBackgroundColor(colors.brown) + term.setTextColor(colors.cyan) + term.write(line) + end + end + + term.redirect(back_term) + os.sleep(1) + end +end) diff --git a/mess/stream_video.py b/mess/stream_video.py new file mode 100644 index 0000000..8cbbcfd --- /dev/null +++ b/mess/stream_video.py @@ -0,0 +1,11 @@ +# x-run: sanic stream_video:app +from sanic import Request, Sanic, Websocket + +app = Sanic("CCVideoStreamer") + +@app.websocket("/stream") +async def ws_stream(req: Request, ws: Websocket): + with open("./video.cani", "r") as fp: + for line in fp: + await ws.send(bytes.fromhex(line.strip())) + await ws.close() diff --git a/mess/streamplay.lua b/mess/streamplay.lua new file mode 100644 index 0000000..ebbef06 --- /dev/null +++ b/mess/streamplay.lua @@ -0,0 +1,83 @@ + +local url = "wss://kc.is.being.pet/ws" + +local screen = peripheral.wrap("monitor_1") +local speakers = { + l = peripheral.wrap("speaker_1"), + r = peripheral.wrap("speaker_0"), +} + +local ws = assert(http.websocket(url)) + +local function parse_u16(data) + local v = table.remove(data, 1) + v = bit.bor(v, bit.blshift(table.remove(data, 1), 8)) + return v, data +end + +local metadata_pkt = ws.receive() +metadata_pkt = { string.byte(metadata_pkt, 1, #metadata_pkt) } +local framerate = table.remove(metadata_pkt, 1) +local n_channels = table.remove(metadata_pkt, 1) +local sample_rate, data = parse_u16(metadata_pkt) +local screen_w, data = parse_u16(data) +local screen_h, data = parse_u16(data) +local samples_per_frame = math.floor(sample_rate / framerate) + +print(string.format("FPS: %d", framerate)) +print(string.format("Audio: %d channels, %d Hz", n_channels, sample_rate)) +print(string.format("Video: %dx%d", screen_w, screen_h)) +print(string.format("S/F: %d", samples_per_frame)) + +local function decode_s8(buf) + local buffer = {} + for i = 1, #buf do + local v = buf[i] + if bit32.band(v, 0x80) then + v = bit32.bxor(v, 0x7F) - 128 + end + table.insert(buffer, v) + table.insert(buffer, v) + end + return buffer +end + +local function mkSpeakerCoro(p, b) + return function() + while not p.playAudio(b) do + os.pullEvent("speaker_audio_empty") + end + end +end + +while true do + local video_pkt = ws.receive() + local channels = { l = {}, r = {} } + local offset = 1 + channels.l = decode_s8({ string.byte(video_pkt, offset, offset + samples_per_frame - 1) }) + offset = offset + samples_per_frame + channels.r = decode_s8({ string.byte(video_pkt, offset, offset + samples_per_frame - 1) }) + offset = offset + samples_per_frame + for y = 1, screen_h do + local tx, bg, fg = {}, {}, {} + for x = 1, screen_w do + table.insert(tx, string.sub(video_pkt, offset, offset)) + local color = string.byte(video_pkt, offset + 1, offset + 1) + table.insert(bg, string.format("%x", bit.brshift(color, 4))) + table.insert(fg, string.format("%x", bit.band(color, 0xF))) + offset = offset + 2 + end + screen.setCursorPos(1, y) + screen.blit(table.concat(tx), table.concat(bg), table.concat(fg)) + end + + for i = 1, 16 do + local r, g, b = string.byte(video_pkt, offset, offset + 3) + screen.setPaletteColor(bit.blshift(1, i - 1), bit.bor(bit.bor(bit.blshift(r, 16), bit.blshift(g, 8)), b)) + offset = offset + 3 + end + + parallel.waitForAll( + mkSpeakerCoro(speakers.l, channels.l), + mkSpeakerCoro(speakers.r, channels.r)) +end diff --git a/obcb-cc.lua b/obcb-cc.lua index f0e6167..2fe4054 100644 --- a/obcb-cc.lua +++ b/obcb-cc.lua @@ -26,9 +26,12 @@ local function send_chunk_subscribe_request(chunk) ws.send(string.char(0x14, bit.band(chunk, 0xFF), bit.band(bit.brshift(chunk, 8), 0xFF)), true) end +local shutdown = false + parallel.waitForAll(function() - while true do + while not shutdown do local data, is_binary = ws.receive() + if data == nil then return end data = { string.byte(data, 1, #data) } local opcode = table.remove(data, 1) if opcode == 0x00 then -- hello @@ -63,20 +66,25 @@ function() mon.setTextScale(0.5) mon.clear() local tw, th = term.getSize() + term.clear() send_chunk_request(chunk_id) send_chunk_subscribe_request(chunk_id) term.setCursorPos(1, 3) print("Showing chunk " .. chunk_id) print(string.format("Screen: %dx%d", mon.getSize())) - while true do + while not shutdown do local ev = { os.pullEvent() } if ev[1] == "char" and ev[2] == "q" then + shutdown = true + ws.close() break elseif ev[1] == "obcb:hello" then term.setCursorPos(1, 1) + term.clearLine() print(string.format("Hello: obcb v%d.%d", ev[2], ev[3])) elseif ev[1] == "obcb:clients" then term.setCursorPos(1, 2) + term.clearLine() print("Clients: " .. ev[2]) elseif ev[1] == "obcb:update" then for y = 1, 81 do @@ -96,4 +104,5 @@ function() end end) +shutdown = true ws.close() diff --git a/wsvpn.c b/wsvpn.c index 6a3a6dc..57cd1f7 100644 --- a/wsvpn.c +++ b/wsvpn.c @@ -65,10 +65,10 @@ struct client { struct client clients[MAX_CLIENTS] = { 0 }; -static void handle_client(struct mg_connection *connection, int event_type, void *ev_data, void *fn_data); -static void on_ws_connect(struct mg_connection *connection, struct mg_http_message *message, void *data); -static void on_ws_message(struct mg_connection *connection, struct mg_ws_message *message, void *data); -static void on_ws_disconnect(struct mg_connection *connection, void *data); +static void handle_client(struct mg_connection *connection, int event_type, void *ev_data); +static void on_ws_connect(struct mg_connection *connection, struct mg_http_message *message); +static void on_ws_message(struct mg_connection *connection, struct mg_ws_message *message); +static void on_ws_disconnect(struct mg_connection *connection); bool client_is_open(struct client *client, uint16_t channel); static void modem_open(struct client *client, uint16_t request_id, uint16_t channel); @@ -101,15 +101,15 @@ int main(void) { mg_mgr_free(&manager); } -static void handle_client(struct mg_connection *connection, int event_type, void *event_data, void *fn_data) { +static void handle_client(struct mg_connection *connection, int event_type, void *event_data) { if (event_type == MG_EV_OPEN) { if (connection->rem.port == 0) return; memset(connection->data, 0, 32); } else if (event_type == MG_EV_HTTP_MSG) { struct mg_http_message *http_message = (struct mg_http_message *) event_data; - if (mg_http_match_uri(http_message, "/open")) { + if (mg_match(http_message->uri, mg_str_s("/open"), 0)) { mg_ws_upgrade(connection, http_message, NULL); - } else if (mg_http_match_uri(http_message, "/metrics")) { + } else if (mg_match(http_message->uri, mg_str_s("/metrics"), 0)) { mg_printf(connection, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"); mg_http_printf_chunk(connection, "# HELP ws_bytes_sent_total Number of bytes sent to clients\n"); mg_http_printf_chunk(connection, "# TYPE ws_bytes_sent_total counter\n"); @@ -147,13 +147,13 @@ static void handle_client(struct mg_connection *connection, int event_type, void } } else if (event_type == MG_EV_WS_OPEN) { struct mg_http_message *http_message = (struct mg_http_message *) event_data; - on_ws_connect(connection, http_message, fn_data); + on_ws_connect(connection, http_message); } else if (event_type == MG_EV_WS_MSG) { struct mg_ws_message *ws_message = (struct mg_ws_message *)event_data; - on_ws_message(connection, ws_message, fn_data); + on_ws_message(connection, ws_message); } else if (event_type == MG_EV_CLOSE) { if (connection->is_websocket) { - on_ws_disconnect(connection, fn_data); + on_ws_disconnect(connection); } } } @@ -211,9 +211,8 @@ void ws_respond(struct client *client, uint16_t request_id, void *data, uint32_t mg_ws_send(client->connection, buffer, size + 3, WEBSOCKET_OP_BINARY); } -static void on_ws_connect(struct mg_connection *connection, struct mg_http_message *message, void *data) { +static void on_ws_connect(struct mg_connection *connection, struct mg_http_message *message) { (void)message; - (void)data; struct client *client = malloc(sizeof(struct client)); memcpy(&connection->data[0], &client, sizeof(struct client *)); client->connection = connection; @@ -226,9 +225,7 @@ static void on_ws_connect(struct mg_connection *connection, struct mg_http_messa mg_ws_send(connection, buffer, 2 + buffer[1], WEBSOCKET_OP_BINARY); } -static void on_ws_message(struct mg_connection *connection, struct mg_ws_message *message, void *data) { - (void)data; - +static void on_ws_message(struct mg_connection *connection, struct mg_ws_message *message) { if ((message->flags & 15) != WEBSOCKET_OP_BINARY) { const char *err_str = "This server only works in binary mode. Sorry!"; mg_ws_send(connection, err_str, strlen(err_str), WEBSOCKET_OP_TEXT); @@ -244,15 +241,15 @@ static void on_ws_message(struct mg_connection *connection, struct mg_ws_message if (message->data.len == 0) return; - uint16_t request_id = ntohs(*(uint16_t*)&message->data.ptr[1]); + uint16_t request_id = ntohs(*(uint16_t*)&message->data.buf[1]); - switch (message->data.ptr[0]) { + switch (message->data.buf[0]) { case 'I': // info. We can safely ignore that message break; case 'O': // open { metrics.method_calls[0]++; - uint16_t channel = ntohs(*(uint16_t*)&message->data.ptr[3]); + uint16_t channel = ntohs(*(uint16_t*)&message->data.buf[3]); printf("%p[%04x] modem.open(%d)\n", (void*)client, request_id, channel); modem_open(client, request_id, channel); } @@ -260,7 +257,7 @@ static void on_ws_message(struct mg_connection *connection, struct mg_ws_message case 'o': // isOpen { metrics.method_calls[1]++; - uint16_t channel = ntohs(*(uint16_t*)&message->data.ptr[3]); + uint16_t channel = ntohs(*(uint16_t*)&message->data.buf[3]); printf("%p[%04x] modem.isOpen(%d)\n", (void*)client, request_id, channel); modem_isOpen(client, request_id, channel); } @@ -268,7 +265,7 @@ static void on_ws_message(struct mg_connection *connection, struct mg_ws_message case 'c': // close { metrics.method_calls[2]++; - uint16_t channel = ntohs(*(uint16_t*)&message->data.ptr[3]); + uint16_t channel = ntohs(*(uint16_t*)&message->data.buf[3]); printf("%p[%04x] modem.close(%d)\n", (void*)client, request_id, channel); modem_close(client, request_id, channel); } @@ -283,21 +280,20 @@ static void on_ws_message(struct mg_connection *connection, struct mg_ws_message case 'T': // transmit { metrics.method_calls[4]++; - uint16_t channel = ntohs(*(uint16_t*)&message->data.ptr[3]); - uint16_t reply_channel = ntohs(*(uint16_t*)&message->data.ptr[5]); - uint16_t data_length = ntohs(*(uint16_t*)&message->data.ptr[7]); - modem_transmit(client, request_id, channel, reply_channel, (void*)&message->data.ptr[9], data_length); + uint16_t channel = ntohs(*(uint16_t*)&message->data.buf[3]); + uint16_t reply_channel = ntohs(*(uint16_t*)&message->data.buf[5]); + uint16_t data_length = ntohs(*(uint16_t*)&message->data.buf[7]); + modem_transmit(client, request_id, channel, reply_channel, (void*)&message->data.buf[9], data_length); } return; default: - ws_send_error(client, request_id, "Unknown opcode: 0x%02x", message->data.ptr[0]); + ws_send_error(client, request_id, "Unknown opcode: 0x%02x", message->data.buf[0]); connection->is_draining = 1; return; } } -static void on_ws_disconnect(struct mg_connection *connection, void *data) { - (void)data; +static void on_ws_disconnect(struct mg_connection *connection) { struct client *client = *(struct client **)&connection->data[0]; if (client->connection == connection) {