1
0
Fork 0

Compare commits

...

26 Commits

Author SHA1 Message Date
Casey c3b5d1a198
Added verbosity and a progress bar 2024-11-16 14:35:05 +03:00
Casey 743435200d
Added missing random things
Mostly sample images and tiny scripts for weight calculation.
Nothing too of importance
2024-10-05 14:27:07 +03:00
Casey c45e9f88bc
OpenMP, CPIv1 writer and proper size for converter
Also moved *_varint into commons, writers are now separate functions,
maybe I could move them into commons sometime and add readers in case I
would use them at some point.
2024-10-05 12:44:16 +03:00
Casey d0cf362c1b
They use 6x9 font, not 8x11...
- https://github.com/cc-tweaked/CC-Tweaked/issues/1987
- https://github.com/cc-tweaked/CC-Tweaked/blob/mc-1.20.x/projects/common/src/client/java/dan200/computercraft/client/render/text/FixedWidthFontRenderer.java#L38-L39
2024-10-03 22:25:58 +03:00
Casey 36c19b23dc
Made it a bit more silent
This commit message is literally longer than change itself.
2024-10-03 20:39:04 +03:00
Casey d4ca60c9f9
CPI -> PNG renderer
Supports CPIv0 and CPIv1 for now, but other formats are experimental
anyways, so not a big problem
2024-10-03 20:28:49 +03:00
Casey 8589b2a730
Build stb_* stuff into a separate blob
Speeds up build process a little bit because we don't have to build it
every single time
2024-10-03 19:25:34 +03:00
Casey 3cadd81a6b
Added random missing stuff so I won't go crazy 2024-10-03 19:03:22 +03:00
Casey e2e5d57e1f
Moved things to a separate file
Font atlas and palettes could be reused by other programs for CC, so
that makes sense IMO.

Also removed debug image saving in cc-pic.py that was left here for ALL
THAT TIME for some fucking reason.
2024-10-03 18:57:44 +03:00
Casey 23447e7ed9
Fixed wsvpn server to work with latest Mongoose 2024-10-03 15:37:46 +03:00
Casey 022cb0cde8
Merge branch 'vftdan-palette-k-means' 2024-10-03 15:35:35 +03:00
Casey 93205ec237
Merge branch 'palette-k-means' of https://git.being.pet/vftdan/cc-stuff into vftdan-palette-k-means 2024-10-03 15:35:20 +03:00
Vftdan 6028bb419e Avoid duplicate palette entries
Don't make an item an empty cluster warp candidate, if it is already
warp candidate for another cluster
2024-10-03 11:26:45 +02:00
Vftdan 5f95f895d2 Try to avoid having unused palette items when the source image only uses a limited gamut region
Warp each empty cluster's centroid onto the closest dataset item
2024-10-03 10:20:58 +02:00
Vftdan c91f4d79bd Allocate the correct amount of intermediate centroid data 2024-10-03 10:16:57 +02:00
Vftdan c5af524dac Implement k-means palette generation 2024-10-03 01:27:30 +02:00
Vftdan 849563285c Global resizeable palette 2024-10-02 23:57:12 +02:00
Vftdan d9f4362615 Dedicated palette type that also stores own length 2024-10-02 23:17:29 +02:00
Vftdan e8c53b1f9b Fix linking recipe for wsvpn
Only the first object was being added
2024-10-02 21:22:19 +02:00
Vftdan be21d42fa0 Now only calculate color difference once for each palette entry pair
Not clear whether it is more optimal
2024-10-02 19:45:27 +02:00
Vftdan d765e88679 Only calculate difference between each pixel × each palette entry once 2024-10-02 19:11:57 +02:00
Vftdan 1eb767d0d2 Added gitignore with C build artifacts 2024-10-02 18:48:47 +02:00
Vftdan a65943a6bd Added Makefile for img2cpi and wsvpn
FIXME: downgrade mongoose submodule or fix wsvpn
2024-10-02 18:47:09 +02:00
Vftdan 2f80e5327d Added mongoose.ws dependency submodule 2024-10-02 18:45:37 +02:00
Vftdan bbbe20f941 Add stb submodule as dependency 2024-10-02 18:23:18 +02:00
Casey 1eccdde3a3
Moved stuff into separate functions, minor cleanup 2024-10-02 15:31:24 +03:00
31 changed files with 1962 additions and 473 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
img2cpi
img2cpi.o
wsvpn
wsvpn.o
cpi2png
cc-common.o
vim.state
__pycache__

6
.gitmodules vendored Normal file
View File

@ -0,0 +1,6 @@
[submodule "dependencies/stb"]
path = dependencies/stb
url = https://github.com/nothings/stb
[submodule "dependencies/mongoose"]
path = dependencies/mongoose
url = https://github.com/cesanta/mongoose

24
Makefile Normal file
View File

@ -0,0 +1,24 @@
CPPFLAGS += -Idependencies -Idependencies/mongoose
LDLIBS += -lm
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 "$@"
clean:
$(RM) -v img2cpi cpi2png wsvpn wsvpn.o cc-common.o dependencies/stb/*.o
.PHONY: all

327
cc-common.c Normal file
View File

@ -0,0 +1,327 @@
#include "cc-common.h"
int 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 -position / 7;
}
return (position + 7) / 7;
}
int write_varint(FILE *fp, unsigned int in) {
unsigned mask = 0xFFFFFF80;
int written = 0;
while (true) {
if ((in & mask) == 0) {
fputc(in & 0xff, fp);
return written + 1;
}
fputc((in & 0x7F) | 0x80, fp);
written++;
in >>= 7;
}
}
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, },
};

31
cc-common.h Normal file
View File

@ -0,0 +1,31 @@
#ifndef _CC_COMMON_H_
#define _CC_COMMON_H_
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
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;
int read_varint(FILE *fp, unsigned int *out);
int write_varint(FILE *fp, unsigned int in);
#endif

View File

@ -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)

View File

@ -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

BIN
cpi-images/casey.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
cpi-images/cute.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
cpi-images/n25.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

BIN
cpi-images/rando.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

BIN
cpi-images/rat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
cpi-images/wp.cpi Normal file

Binary file not shown.

91
cpi2png.c Normal file
View File

@ -0,0 +1,91 @@
// x-run: make test-cpi2png
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stb/stb_image.h>
#include <stb/stb_image_write.h>
#include <string.h>
#include "cc-common.h"
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) > 0 && "Failed to read width varint");
assert(read_varint(fp_in, &height) > 0 && "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[16] = { 0 };
// 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;
}
unsigned char *buffer = calloc(width * height, 2);
fread(buffer, 2, width * height, fp_in);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
unsigned char sym = buffer[(x + y * width) * 2];
unsigned char color = buffer[(x + y * width) * 2 + 1];
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(canvas);
fclose(fp_in);
return EXIT_SUCCESS;
}

1
dependencies/mongoose vendored Submodule

@ -0,0 +1 @@
Subproject commit 3525f044f551816dc1469f445fc16b94d51a1e78

1
dependencies/stb vendored Submodule

@ -0,0 +1 @@
Subproject commit f75e8d1cad7d90d72ef7a4661f1b994ef78b4e31

833
img2cpi.c
View File

@ -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 <math.h>
#include <assert.h>
#include <stb/stb_image.h>
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include <stb/stb_image_resize2.h>
#include <argp.h>
#include <stdio.h>
@ -11,24 +12,26 @@
#include <string.h>
#include <strings.h>
#include <stdbool.h>
#include "cc-common.h"
#ifdef USE_OPENMP
#include <omp.h>
#endif
#define MAX_COLOR_DIFFERENCE 768
#define K_MEANS_ITERATIONS 4
#define PROGRESS_BAR_WIDTH 24
#define TOSTRNAME(M) #M
#define TOSTR(M) TOSTRNAME(M)
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;
};
const extern char font_atlas[256][11];
const extern union color DEFAULT_PALETTE[16], DEFAULT_GRAY_PALETTE[16];
struct arguments {
bool fast_mode;
bool verbose;
int width, height;
enum cpi_version {
CPI_VERSION_AUTO,
@ -57,6 +60,7 @@ struct arguments {
char *output_path;
} args = {
.fast_mode = false,
.verbose = false,
.width = 4 * 8 - 1, // 4x3 blocks screen
.height = 3 * 6 - 2,
.cpi_version = CPI_VERSION_AUTO,
@ -64,8 +68,7 @@ struct arguments {
.input_path = NULL,
.output_path = NULL,
.palette = NULL,
.palette_type = PALETTE_DEFAULT // TODO(kc): change to PALETTE_AUTO when
// k-means is implemented
.palette_type = PALETTE_AUTO
};
struct image {
@ -76,19 +79,53 @@ struct image {
struct image_pal {
int w, h;
uint8_t *pixels;
const struct palette *palette;
};
struct k_means_state {
const struct image *items;
struct palette *clusters;
uint8_t *predicted_cluster;
struct k_means_centroid_intermediate {
struct {
float r, g, b;
} sums;
size_t count;
union color closest_present_item;
float closest_present_distance;
} *centroid_intermediate;
size_t item_count;
};
bool parse_cmdline(int argc, char **argv);
void show_help(const char *progname, bool show_all, FILE *fp);
struct image *image_load(const char *fp);
struct image *image_new(int w, int h);
struct image *image_resize(struct image *original, int new_w, int new_h);
struct image_pal *image_quantize(struct image *original, const union color *colors, size_t n_colors);
struct image_pal *image_quantize(struct image *original, const struct palette *palette);
void image_unload(struct image *img);
float get_color_difference(union color a, union color b);
float get_color_brightness(union color clr);
void image_unload(struct image *img);
void get_size_keep_aspect(int w, int h, int dw, int dh, int *ow, int *oh);
void convert_2x3(const struct image_pal *img, struct cc_char *characters);
void convert_6x9(const struct image_pal *img, struct cc_char *characters);
int save_cpi_0(FILE *fp, const struct palette *pal, const struct cc_char *chars, int w, int h);
int save_cpi_1(FILE *fp, const struct palette *pal, const struct cc_char *chars, int w, int h);
// Only one global custom palette is maintained
struct palette *custom_palette_resize(uint8_t size);
struct palette *custom_palette_from(const struct palette *orig);
struct k_means_state k_means_init(const struct image *image, struct palette *starting_palette);
bool k_means_iteration(struct k_means_state *state);
void k_means_end(struct k_means_state *state);
struct palette *palette_k_means(const struct image *image, const struct palette *prototype);
const char *known_file_extensions[] = {
".png", ".jpg", ".jpeg", ".jfif", ".jpg", ".gif",
".tga", ".bmp", ".hdr", ".pnm", 0
@ -103,6 +140,7 @@ static const struct optiondocs {
} optiondocs[] = {
{ 'h', "help", 0, "Show help", 0 },
{ 'f', "fast", 0, "Use fast (old) method for picking characters and colors", 0 },
{ 'v', "verbose", 0, "Increase verbosity", 0 },
{ 'W', "width", "width", "Width in characters", 0 },
{ 'h', "height", "height", "Height in characters", 0 },
{ 'P', "palette", "palette", "Use specific palette.\n"
@ -133,6 +171,8 @@ static const struct optiondocs {
{ 0 }
};
static char progress_bar[PROGRESS_BAR_WIDTH];
int main(int argc, char **argv) {
if (!parse_cmdline(argc, argv)) {
show_help(argv[0], false, stderr);
@ -145,11 +185,16 @@ int main(int argc, char **argv) {
return EXIT_FAILURE;
}
if (args.verbose) {
memset(progress_bar, '#', PROGRESS_BAR_WIDTH);
printf("Input image: %dx%d\n", src_image->w, src_image->h);
}
struct image *canvas;
if (args.fast_mode) {
canvas = image_new(args.width * 2, args.height * 3);
} else {
canvas = image_new(args.width * 8, args.height * 11);
canvas = image_new(args.width * 6, args.height * 9);
}
if (!canvas) {
@ -157,14 +202,18 @@ int main(int argc, char **argv) {
return EXIT_FAILURE;
}
if (args.verbose) {
printf("Output image canvas: %dx%d\n", canvas->w, canvas->h);
}
// TODO: load palette, maybe calculate it too? k-means?
const union color *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: assert(0 && "Not implemented"); break;
case PALETTE_LIST: assert(0 && "Not implemented"); break;
case PALETTE_PATH: assert(0 && "Not implemented"); 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; // TODO
case PALETTE_PATH: assert(0 && "Not implemented"); break; // TODO
default: assert(0 && "Unreachable");
}
@ -174,6 +223,10 @@ int main(int argc, char **argv) {
int new_w, new_h;
get_size_keep_aspect(src_image->w, src_image->h, canvas->w, canvas->h, &new_w, &new_h);
if (args.verbose) {
printf("Scaling down to: %dx%d\n", new_w, new_h);
}
scaled_image = image_resize(src_image, new_w, new_h);
if (!scaled_image) {
fprintf(stderr, "Error: failed to open the file\n");
@ -190,131 +243,42 @@ int main(int argc, char **argv) {
small_w * sizeof(union color));
}
// TODO: actually do stuff
struct cc_char *characters = calloc(args.width * args.height, sizeof(struct cc_char));
struct image_pal *quantized_image = image_quantize(canvas, palette, 16);
struct image_pal *quantized_image = image_quantize(canvas, palette);
if (!quantized_image) {
fprintf(stderr, "Error: failed to open the file\n");
return EXIT_FAILURE;
}
FILE *tmp = fopen("/tmp/img.raw", "wb");
for (int i = 0; i < quantized_image->w * quantized_image->h; i++) {
union color pix = palette[quantized_image->pixels[i]];
fputc(pix.rgba.r, tmp);
fputc(pix.rgba.g, tmp);
fputc(pix.rgba.b, tmp);
if (args.verbose) {
printf("Converting image ");
}
fclose(tmp);
if (args.fast_mode) {
// use old 2x3
for (int y = 0; y < args.height; y++) {
for (int x = 0; x < args.width; x++) {
unsigned char darkest_i = 0, brightest_i = 0;
float darkest_diff = 0xffffff, brightest_diff = 0;
for (int oy = 0; oy < 3; oy++) {
for (int ox = 0; ox < 2; ox++) {
unsigned char pix = quantized_image->pixels[ox + (x + (y * 3 + oy) * args.width) * 2];
float brightness = get_color_brightness(palette[pix]);
if (brightness >= brightest_diff) {
brightest_i = pix;
brightest_diff = brightness;
}
if (brightness <= darkest_diff) {
darkest_i = pix;
darkest_diff = brightness;
}
}
}
unsigned char bitmap = 0;
const static unsigned char pixel_bits[3][2] = { { 1, 2}, { 4, 8 }, { 16, 0 } };
for (int oy = 0; oy < 3; oy++) {
for (int ox = 0; ox < 2; ox++) {
if (ox == 1 && oy == 2) continue;
unsigned char pix = quantized_image->pixels[ox + (x + (y * 3 + oy) * args.width) * 2];
float diff_bg = get_color_difference(palette[darkest_i], palette[pix]);
float diff_fg = get_color_difference(palette[brightest_i], palette[pix]);
if (diff_fg < diff_bg) {
bitmap |= pixel_bits[oy][ox];
}
}
}
{
unsigned char pix = quantized_image->pixels[1 + (x + (y * 3 + 2) * args.width) * 2];
float diff_bg = get_color_difference(palette[darkest_i], palette[pix]);
float diff_fg = get_color_difference(palette[brightest_i], palette[pix]);
if (diff_fg < diff_bg) {
bitmap ^= 31;
unsigned char tmp = darkest_i;
darkest_i = brightest_i;
brightest_i = tmp;
}
}
characters[x + y * args.width].character = 0x80 + bitmap;
characters[x + y * args.width].bg = darkest_i;
characters[x + y * args.width].fg = brightest_i;
}
if (args.verbose) {
printf(" using fast method\n");
}
convert_2x3(quantized_image, characters);
} else {
// use new 8x11 character matching
for (int y = 0; y < args.height; y++) {
for (int x = 0; x < args.width; x++) {
// Oh boy...
float min_diff = 0xffffff;
char closest_sym = 0x00, closest_color = 0xae;
for (int sym = 0x01; sym <= 0xFF; sym++) {
if (sym == '\t' || sym == '\n' || sym == '\r' || sym == '\x0e') {
continue;
}
for (int color = 0x00; color <= 0xff; color++) {
union color cell_bg = palette[color & 0xF],
cell_fg = palette[color >> 4];
float difference = 0;
for (int oy = 0; oy < 11; oy++) {
unsigned char sym_line = font_atlas[sym][oy];
for (int ox = 0; ox < 8; ox++) {
bool lit = sym_line & (0x80 >> ox);
union color pixel = palette[quantized_image->pixels[
ox + (x + (y * 11 + oy) * args.width) * 8
]];
difference += get_color_difference(pixel, lit ? cell_fg : cell_bg);
}
}
if (difference <= min_diff) {
min_diff = difference;
closest_sym = sym;
closest_color = color;
}
}
}
characters[x + y * args.width].character = closest_sym;
characters[x + y * args.width].bg = closest_color & 0xF;
characters[x + y * args.width].fg = closest_color >> 4;
}
if (args.verbose) {
printf(" using slow method\n");
}
convert_6x9(quantized_image, characters);
}
// TODO: implement something other than CPIv0
FILE *fp = fopen(args.output_path, "wb");
fwrite("CCPI", 1, 4, fp);
fputc(args.width, fp);
fputc(args.height, fp);
fputc(0x00, fp);
for (int i = 0; i < 16; i++) {
fputc(palette[i].rgba.r, fp);
fputc(palette[i].rgba.g, fp);
fputc(palette[i].rgba.b, fp);
if (args.verbose) {
printf("Conversion done, saving image ");
}
for (int i = 0; i < args.width * args.height; i++) {
fputc(characters[i].character, fp);
fputc(characters[i].bg | (characters[i].fg << 4), fp);
FILE *fp = fopen(args.output_path, "wb");
if (args.width < 256 && args.height < 256) {
printf(" using cpiv0\n");
save_cpi_0(fp, palette, characters, args.width, args.height);
} else {
printf(" using cpiv1\n");
save_cpi_1(fp, palette, characters, args.width, args.height);
}
fclose(fp);
@ -323,6 +287,47 @@ int main(int argc, char **argv) {
return EXIT_SUCCESS;
}
int _write_palette_full(FILE *fp, const struct palette *pal) {
int written = 0;
assert(pal->count == 16 && "Invalid palette size");
for (int i = 0; i < 16; i++) {
written += fputc(pal->colors[i].rgba.r, fp);
written += fputc(pal->colors[i].rgba.g, fp);
written += fputc(pal->colors[i].rgba.b, fp);
}
return written;
}
int _write_pixeldata_v0(FILE *fp, const struct cc_char *chars, int w, int h) {
int written = 0;
for (int i = 0; i < w * h; i++) {
written += fputc(chars[i].character, fp);
written += fputc(chars[i].bg | (chars[i].fg << 4), fp);
}
return written;
}
int save_cpi_0(FILE *fp, const struct palette *pal, const struct cc_char *chars, int w, int h) {
int written = 0;
written += fwrite("CCPI", 1, 4, fp);
written += fputc(w, fp);
written += fputc(h, fp);
written += fputc(0x00, fp);
written += _write_palette_full(fp, pal);
written += _write_pixeldata_v0(fp, chars, w, h);
return written;
}
int save_cpi_1(FILE *fp, const struct palette *pal, const struct cc_char *chars, int w, int h) {
int written = 0;
written += fwrite("CPI\x01", 1, 4, fp);
written += write_varint(fp, w);
written += write_varint(fp, h);
written += _write_palette_full(fp, pal);
written += _write_pixeldata_v0(fp, chars, w, h);
return written;
}
bool parse_cmdline(int argc, char **argv) {
static struct option options[] = {
{ "help", no_argument, 0, 'h' },
@ -337,7 +342,7 @@ bool parse_cmdline(int argc, char **argv) {
while (true) {
int option_index = 0;
int c = getopt_long(argc, argv, "hfW:H:V:p:P:", options, &option_index);
int c = getopt_long(argc, argv, "hvfW:H:V:p:P:", options, &option_index);
if (c == -1) break;
if (c == 0) c = options[option_index].val;
if (c == '?') break;
@ -353,6 +358,9 @@ bool parse_cmdline(int argc, char **argv) {
fprintf(stderr, "Warning: text mode ignores version\n");
}
break;
case 'v': // --verbose
args.verbose = true;
break;
case 'W': // --width
args.width = atoi(optarg);
break;
@ -577,17 +585,18 @@ void get_size_keep_aspect(int w, int h, int dw, int dh, int *ow, int *oh)
}
}
struct image_pal *image_quantize(struct image *original, const union color *colors, size_t n_colors) {
struct image_pal *image_quantize(struct image *original, const struct palette *palette) {
struct image_pal *out = calloc(1, sizeof(struct image_pal));
out->w = original->w;
out->h = original->h;
out->pixels = calloc(original->w, original->h);
out->palette = palette;
for (int i = 0; i < out->w * out->h; i++) {
int closest_color = 0;
float closest_distance = 1e20;
for (int color = 0; color < n_colors; color++) {
float dist = get_color_difference(colors[color], original->pixels[i]);
for (int color = 0; color < palette->count; color++) {
float dist = get_color_difference(palette->colors[color], original->pixels[i]);
if (dist <= closest_distance) {
closest_distance = dist;
closest_color = color;
@ -610,297 +619,269 @@ float get_color_brightness(union color clr) {
return get_color_difference(clr, (union color){ .v = 0 });
}
const union color DEFAULT_PALETTE[16] = {
{ 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[16] = {
{ 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 }
};
void convert_2x3(const struct image_pal *img, struct cc_char *characters) {
int w = img->w / 2, h = img->h / 3;
for (int y = 0; y < h; y++) {
if (args.verbose) {
int sz = PROGRESS_BAR_WIDTH - (y * PROGRESS_BAR_WIDTH / h);
printf("\r[%-" TOSTR(PROGRESS_BAR_WIDTH) ".*s|%7.3f%%|%4d/%4d]",
PROGRESS_BAR_WIDTH - sz, progress_bar + sz,
100.0 * (y + 1) / h,
y + 1, h);
fflush(stdout);
}
for (int x = 0; x < w; x++) {
unsigned char darkest_i = 0, brightest_i = 0;
float darkest_diff = 0xffffff, brightest_diff = 0;
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, },
};
for (int oy = 0; oy < 3; oy++) {
for (int ox = 0; ox < 2; ox++) {
unsigned char pix = img->pixels[ox + (x + (y * 3 + oy) * w) * 2];
float brightness = get_color_brightness(img->palette->colors[pix]);
if (brightness >= brightest_diff) {
brightest_i = pix;
brightest_diff = brightness;
}
if (brightness <= darkest_diff) {
darkest_i = pix;
darkest_diff = brightness;
}
}
}
unsigned char bitmap = 0;
const static unsigned char pixel_bits[3][2] = { { 1, 2}, { 4, 8 }, { 16, 0 } };
for (int oy = 0; oy < 3; oy++) {
for (int ox = 0; ox < 2; ox++) {
if (ox == 1 && oy == 2) continue;
unsigned char pix = img->pixels[ox + (x + (y * 3 + oy) * w) * 2];
float diff_bg = get_color_difference(img->palette->colors[darkest_i], img->palette->colors[pix]);
float diff_fg = get_color_difference(img->palette->colors[brightest_i], img->palette->colors[pix]);
if (diff_fg < diff_bg) {
bitmap |= pixel_bits[oy][ox];
}
}
}
{
unsigned char pix = img->pixels[1 + (x + (y * 3 + 2) * w) * 2];
float diff_bg = get_color_difference(img->palette->colors[darkest_i], img->palette->colors[pix]);
float diff_fg = get_color_difference(img->palette->colors[brightest_i], img->palette->colors[pix]);
if (diff_fg < diff_bg) {
bitmap ^= 31;
unsigned char tmp = darkest_i;
darkest_i = brightest_i;
brightest_i = tmp;
}
}
characters[x + y * w].character = 0x80 + bitmap;
characters[x + y * w].bg = darkest_i;
characters[x + y * w].fg = brightest_i;
}
}
if (args.verbose) {
printf("\n");
}
}
void convert_6x9(const struct image_pal *img, struct cc_char *characters) {
int w = img->w / 6, h = img->h / 9;
float palette_self_diffs[0x100][0x10] = {{(float) 0xffffff}};
for (int input_color = 0x0; input_color < 0x100 && input_color < img->palette->count; input_color++) {
for (int output_color = 0x0; output_color < 0x10 && output_color < img->palette->count; output_color++) {
palette_self_diffs[input_color][output_color] = get_color_difference(img->palette->colors[input_color], img->palette->colors[output_color]);
}
}
for (int y = 0; y < h; y++) {
if (args.verbose) {
int sz = PROGRESS_BAR_WIDTH - (y * PROGRESS_BAR_WIDTH / h);
printf("\r[%-" TOSTR(PROGRESS_BAR_WIDTH) ".*s|%7.3f%%|%4d/%4d]",
PROGRESS_BAR_WIDTH - sz, progress_bar + sz,
100.0 * (y + 1) / h,
y + 1, h);
fflush(stdout);
}
#ifdef USE_OPENMP
#pragma omp parallel for
#endif
for (int x = 0; x < w; x++) {
float chunk_palette_diffs[6][9][0x10] = {{{(float) 0xffffff}}};
for (int ox = 0; ox < 6; ox++) {
for (int oy = 0; oy < 9; oy++) {
uint8_t pixel_unresolved = img->pixels[
ox + (x + (y * 9 + oy) * w) * 6
];
for (int color = 0x0; color < 0x10 && color < img->palette->count; color++) {
chunk_palette_diffs[ox][oy][color] = palette_self_diffs[pixel_unresolved][color];
}
}
}
float min_diff = 0xffffff;
char closest_sym = 0x00, closest_color = 0xae;
for (int sym = 0x01; sym <= 0xFF; sym++) {
if (sym == '\t' || sym == '\n' || sym == '\r' || sym == '\x0e') {
continue;
}
for (int color = 0x00; color <= 0xff; color++) {
float difference = 0;
for (int oy = 0; oy < 9; oy++) {
unsigned char sym_line = cc_font_atlas[sym][oy];
for (int ox = 0; ox < 6; ox++) {
bool lit = sym_line & (0x80 >> ox);
difference += chunk_palette_diffs[ox][oy][lit ? color >> 4 : color & 0xF];
}
}
if (difference <= min_diff) {
min_diff = difference;
closest_sym = sym;
closest_color = color;
}
}
}
characters[x + y * w].character = closest_sym;
characters[x + y * w].bg = closest_color & 0xF;
characters[x + y * w].fg = closest_color >> 4;
}
}
if (args.verbose) {
printf("\n");
}
}
struct {
uint8_t count;
union color colors[256];
} custom_palette_data;
struct palette *custom_palette_resize(uint8_t size) {
custom_palette_data.count = size;
return (struct palette*)&custom_palette_data;
}
struct palette *custom_palette_from(const struct palette *orig) {
custom_palette_data.count = orig->count;
for (int i = 0; i < custom_palette_data.count; i++) {
custom_palette_data.colors[i] = orig->colors[i];
}
return (struct palette*)&custom_palette_data;
}
struct k_means_state k_means_init(const struct image *image, struct palette *starting_palette) {
size_t item_count = image->w * image->h;
uint8_t cluster_count = starting_palette->count;
struct k_means_state state = {
.items = image,
.clusters = starting_palette,
.predicted_cluster = calloc(image->w, image->h),
.centroid_intermediate = calloc(cluster_count, sizeof(struct k_means_centroid_intermediate)),
.item_count = item_count,
};
if (state.centroid_intermediate) {
for (size_t i = 0; i < cluster_count; i++) {
state.centroid_intermediate[i].closest_present_item = starting_palette->colors[i];
state.centroid_intermediate[i].closest_present_distance = 1e20;
}
}
return state;
}
bool k_means_iteration(struct k_means_state *state) {
if (!state->predicted_cluster || !state->centroid_intermediate) {
return false;
}
bool changed = false;
// Find closest cluster
for (int i = 0; i < state->item_count; i++) {
int closest_cluster = 0;
float closest_distance = 1e20;
for (int cluster = 0; cluster < state->clusters->count; cluster++) {
union color item = state->items->pixels[i];
float dist = get_color_difference(state->clusters->colors[cluster], item);
if (dist <= closest_distance) {
closest_distance = dist;
closest_cluster = cluster;
}
if (dist < state->centroid_intermediate[cluster].closest_present_distance) {
bool can_update = true;
for (int other_cluster = 0; other_cluster < state->clusters->count; other_cluster++) {
if (other_cluster == cluster) {
continue;
}
if (state->centroid_intermediate[other_cluster].closest_present_item.v == item.v) {
can_update = false;
break;
}
}
if (can_update) {
state->centroid_intermediate[cluster].closest_present_item = item;
state->centroid_intermediate[cluster].closest_present_distance = dist;
}
}
}
if (!changed) {
changed = state->predicted_cluster[i] != closest_cluster;
}
state->predicted_cluster[i] = closest_cluster;
state->centroid_intermediate[closest_cluster].count += 1;
state->centroid_intermediate[closest_cluster].sums.r += state->items->pixels[i].rgba.r;
state->centroid_intermediate[closest_cluster].sums.g += state->items->pixels[i].rgba.g;
state->centroid_intermediate[closest_cluster].sums.b += state->items->pixels[i].rgba.b;
}
// Update centroids
for (int i = 0; i < state->clusters->count; ++i) {
struct k_means_centroid_intermediate intermediate = state->centroid_intermediate[i];
if (intermediate.count) {
union color centroid = {
.rgba = {
.r = intermediate.sums.r / intermediate.count,
.g = intermediate.sums.g / intermediate.count,
.b = intermediate.sums.b / intermediate.count,
.a = 0xff,
}
};
if (!changed) {
changed = state->clusters->colors[i].v != centroid.v;
}
state->clusters->colors[i] = centroid;
} else {
// No pixels are closest to this color
// Warp the centroid onto the closest item
state->clusters->colors[i] = intermediate.closest_present_item;
}
state->centroid_intermediate[i] = (struct k_means_centroid_intermediate) { .sums = {0, 0, 0}, .count = 0, .closest_present_item = state->clusters->colors[i], .closest_present_distance = 1e20 };
}
return changed;
}
void k_means_end(struct k_means_state *state) {
if (state->predicted_cluster) {
free(state->predicted_cluster);
}
if (state->centroid_intermediate) {
free(state->centroid_intermediate);
}
}
struct palette *palette_k_means(const struct image *image, const struct palette *prototype) {
if (!prototype) {
prototype = &cc_default_palette;
}
struct palette *palette = custom_palette_from(prototype);
struct k_means_state state = k_means_init(image, palette);
for (int i = 0; i < K_MEANS_ITERATIONS; i++) {
if (!k_means_iteration(&state)) {
fprintf(stderr, "early k-means stop at iteration %d\n", i);
break;
}
}
k_means_end(&state);
return palette;
}

BIN
mess/avg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 B

82
mess/cc-concat.lua Normal file
View File

@ -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()

BIN
mess/cc_font.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

25
mess/gen-weights.py Normal file
View File

@ -0,0 +1,25 @@
from PIL import Image
from collections import Counter
with Image.open("./cc_font.png") as im:
pixels = im.load()
weights = [0 for _ in range(6 * 9)]
for char in range(256):
ctx, cty = (char % 16) * 8, (char // 16) * 11
for oy in range(9):
for ox in range(6):
pix = int(pixels[ctx + ox + 1, cty + oy + 1][0]) # type: ignore
weights[ox + 6 * oy] += 1 if pix else 0
with Image.new("L", (6, 9), 0) as im_out:
for y in range(9):
for x in range(6):
print("%3d" % weights[x + 6 * y], end="\t")
im_out.putpixel((x, y), weights[x + 6 * y])
print()
im_out.save("avg.png")
print(dict(enumerate([
iv[0] for iv in sorted(enumerate(weights), key=lambda iv: iv[1])
])))

65
mess/libcloudcatcher.py Normal file
View File

@ -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]

150
mess/package_video.py Normal file
View File

@ -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("<HHH", self.sample_rate, self.screen_width, self.screen_height)
@dataclass
class VideoFrame:
audio: list[bytes]
video: list[bytes]
palette: list[int]
def serialize(self) -> 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)

36
mess/pipez.lua Normal file
View File

@ -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)

21
mess/ramfsd.lua Normal file
View File

@ -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)

84
mess/restock.json Normal file
View File

@ -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 }
}
}

434
mess/restock.lua Normal file
View File

@ -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)

11
mess/stream_video.py Normal file
View File

@ -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()

83
mess/streamplay.lua Normal file
View File

@ -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

View File

@ -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()

50
wsvpn.c
View File

@ -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) {