2020-02-03 18:52:53 +04:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
'''
|
2020-02-16 19:00:37 +04:00
|
|
|
|
Slice an image (or multiple images) into 8x8 chunks (tiles) and deduplicate
|
|
|
|
|
them to create a color font and a sequence of characters that represents the
|
|
|
|
|
original image(s).
|
2020-02-03 18:52:53 +04:00
|
|
|
|
'''
|
|
|
|
|
import sys
|
|
|
|
|
from PIL import Image
|
|
|
|
|
import struct
|
|
|
|
|
|
|
|
|
|
BW=8 # tile width
|
|
|
|
|
BH=8 # tile height
|
2020-02-16 19:00:37 +04:00
|
|
|
|
BG=(0,0,0) # RGB color for background (hardcoded, could be configurable)
|
2020-02-03 18:52:53 +04:00
|
|
|
|
|
|
|
|
|
def rgb565(color):
|
|
|
|
|
'''Truncate RGB888[8] color to RGB565'''
|
|
|
|
|
return ((color[0] >> 3) << 11) | ((color[1] >> 2) << 5) | (color[2] >> 3)
|
|
|
|
|
|
|
|
|
|
def extract_block(img, coord):
|
|
|
|
|
'''Extract a RGB block from an image.'''
|
|
|
|
|
data = []
|
|
|
|
|
for yi in range(0, BH):
|
|
|
|
|
row = []
|
|
|
|
|
for xi in range(0, BW):
|
|
|
|
|
try:
|
|
|
|
|
row.append(img.getpixel((coord[0] + xi, coord[1] + yi)))
|
|
|
|
|
except IndexError:
|
|
|
|
|
row.append(BG)
|
|
|
|
|
data.append(row)
|
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
def encode_block(block):
|
|
|
|
|
'''Encode RGB block to 32-bit column-swizzled RGB565'''
|
|
|
|
|
out = []
|
|
|
|
|
for yi in range(0, BH):
|
|
|
|
|
for xi in range(0, BW//2):
|
|
|
|
|
out.append(
|
2020-11-06 10:32:23 +04:00
|
|
|
|
(rgb565(block[yi][xi*2 + 0]) << 0) |
|
|
|
|
|
rgb565(block[yi][xi*2 + 1]) << 16)
|
2020-02-03 18:52:53 +04:00
|
|
|
|
return tuple(out)
|
|
|
|
|
|
2020-02-16 19:00:37 +04:00
|
|
|
|
assert(len(sys.argv) >= 3)
|
2020-02-09 18:13:22 +04:00
|
|
|
|
infiles = sys.argv[1:-1]
|
|
|
|
|
outfile = sys.argv[-1]
|
2020-02-03 18:52:53 +04:00
|
|
|
|
|
2020-02-09 18:13:22 +04:00
|
|
|
|
images = [(infile, Image.open(infile)) for infile in infiles]
|
2020-02-03 18:52:53 +04:00
|
|
|
|
|
|
|
|
|
# character set, addressed by content
|
|
|
|
|
charset = {}
|
|
|
|
|
|
|
|
|
|
# add empty block as character 0
|
|
|
|
|
empty_block = encode_block([[BG]*BW]*BH)
|
|
|
|
|
charset[empty_block] = 0
|
|
|
|
|
|
2020-02-16 19:00:37 +04:00
|
|
|
|
# sequence of characters to represent every image
|
2020-02-09 18:13:22 +04:00
|
|
|
|
seq = []
|
|
|
|
|
for (infile, img) in images:
|
|
|
|
|
blocks_x = (img.size[0] + (BW-1))//BW
|
|
|
|
|
blocks_y = (img.size[1] + (BH-1))//BH
|
|
|
|
|
|
|
|
|
|
print(f'{infile}: {blocks_x}×{blocks_y}')
|
|
|
|
|
|
|
|
|
|
out = []
|
|
|
|
|
for by in range(0, blocks_y):
|
|
|
|
|
row = []
|
|
|
|
|
for bx in range(0, blocks_x):
|
|
|
|
|
bd = encode_block(extract_block(img, (bx * BW, by * BH)))
|
|
|
|
|
try:
|
2020-02-16 19:00:37 +04:00
|
|
|
|
# re-use existing matching character
|
2020-02-09 18:13:22 +04:00
|
|
|
|
ch = charset[bd]
|
|
|
|
|
except KeyError:
|
2020-02-16 19:00:37 +04:00
|
|
|
|
# add character to character set
|
2020-02-09 18:13:22 +04:00
|
|
|
|
ch = len(charset)
|
|
|
|
|
charset[bd] = ch
|
|
|
|
|
row.append(ch)
|
|
|
|
|
out.append(row)
|
2020-02-16 19:00:37 +04:00
|
|
|
|
seq.append((out, blocks_x, blocks_y))
|
2020-02-03 18:52:53 +04:00
|
|
|
|
|
|
|
|
|
m = len(empty_block)
|
|
|
|
|
n = len(charset)
|
|
|
|
|
print(f'used {n} characters')
|
|
|
|
|
|
|
|
|
|
charset_by_ch = [None] * n
|
|
|
|
|
for (bd, ch) in charset.items():
|
|
|
|
|
charset_by_ch[ch] = bd
|
|
|
|
|
|
|
|
|
|
with open(outfile, 'w') as f:
|
|
|
|
|
f.write(f'/* Auto-generated from {infile} by gencolorfont.py */\n')
|
2020-02-09 18:13:22 +04:00
|
|
|
|
f.write('#[rustfmt::skip]\n')
|
2020-02-03 19:38:17 +04:00
|
|
|
|
f.write(f'pub static FONT: [[u32; {m}]; {n}] = [\n')
|
2020-02-03 18:52:53 +04:00
|
|
|
|
for bd in charset_by_ch:
|
|
|
|
|
f.write(' [')
|
|
|
|
|
for val in bd:
|
|
|
|
|
f.write(f'0x{val:08x}, ')
|
|
|
|
|
f.write('],\n')
|
|
|
|
|
|
|
|
|
|
f.write('];\n')
|
|
|
|
|
f.write('\n')
|
|
|
|
|
|
|
|
|
|
# TODO: output sequence; RLE encoding of some kind?
|
2020-02-16 19:00:37 +04:00
|
|
|
|
for (i,(out,blocks_x,blocks_y)) in enumerate(seq):
|
2020-02-09 18:13:22 +04:00
|
|
|
|
f.write('#[rustfmt::skip]\n')
|
|
|
|
|
f.write(f'pub static SEQ{i}: [[u16; {blocks_x}]; {blocks_y}] = [\n')
|
|
|
|
|
for subseq in out:
|
|
|
|
|
f.write(' [')
|
|
|
|
|
for val in subseq:
|
|
|
|
|
f.write(f'0x{val:04x}, ')
|
|
|
|
|
f.write('],\n')
|
|
|
|
|
f.write('];\n')
|