ComputerCraft Paletted Image thingie

This commit is contained in:
Casey 2023-10-15 03:13:42 +03:00
parent 89f4685a22
commit 0c79a8017c
Signed by: hkc
GPG Key ID: F0F6CFE11CDB0960
3 changed files with 197 additions and 0 deletions

124
cc-pic.py Normal file
View File

@ -0,0 +1,124 @@
#!/usr/bin/env python3
# x-run: python3 % ~/downloads/kanade/6bf3cdae12b75326e3c23af73105e5781e42e94e.jpg
from typing import BinaryIO, TextIO
from PIL import Image
from sys import argv
class Converter:
CC_COLORS = [
("0", "colors.white"),
("1", "colors.orange"),
("2", "colors.magenta"),
("3", "colors.lightBlue"),
("4", "colors.yellow"),
("5", "colors.lime"),
("6", "colors.pink"),
("7", "colors.gray"),
("8", "colors.lightGray"),
("9", "colors.cyan"),
("a", "colors.purple"),
("b", "colors.blue"),
("c", "colors.brown"),
("d", "colors.green"),
("e", "colors.red"),
("f", "colors.black"),
]
PIX_BITS = [
[ 1, 2 ],
[ 4, 8 ],
[ 16, 0 ]
]
MAX_DIFF = (3 ** 0.5) * 255
def __init__(self, image: Image.Image, size: tuple[int, int]):
self._img = image.copy()
self._img.thumbnail(( size[0] * 2, size[1] * 3 ))
self._img = self._img.convert("P", palette=Image.ADAPTIVE, colors=16)
self._palette: list[int] = self._img.getpalette() # type: ignore
def _brightness(self, i: int) -> float:
r, g, b = self._palette[i * 3 : (i + 1) * 3]
return (r + g + b) / 768
def _distance(self, a: int, b: int) -> float:
r1, g1, b1 = self._palette[a * 3 : (a + 1) * 3]
r2, g2, b2 = self._palette[b * 3 : (b + 1) * 3]
return ((r1 - r2) ** 2 + (g1 - g2) ** 2 + (b1 - b2) ** 2) ** 0.5 / self.MAX_DIFF
def _get_colors(self, x: int, y: int) -> tuple[int, int]:
brightest_i, brightest_l = 0, 0
darkest_i, darkest_l = 0, 768
for oy, line in enumerate(self.PIX_BITS):
for ox, bit in enumerate(line):
pix = self._img.getpixel((x + ox, y + oy))
brightness = self._brightness(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
def _is_darker(self, bg: int, fg: int, c: int) -> bool:
return self._distance(bg, c) < self._distance(fg, c)
def _get_block(self, x: int, y: int) -> tuple[int, int, int]:
dark_i, bri_i = self._get_colors(x, y)
out: int = 0
for oy, line in enumerate(self.PIX_BITS):
for ox, bit in enumerate(line):
if not self._is_darker(dark_i, bri_i, self._img.getpixel((x + ox, y + oy))):
out |= bit
return out, dark_i, bri_i
def export_binary(self, io: BinaryIO):
io.write(b"CCPI")
io.write(bytes([self._img.width // 2, self._img.height // 3, 0]))
io.write(bytes(self._palette))
for y in range(0, self._img.height - 2, 3):
for x in range(0, self._img.width - 1, 2):
ch, bg, fg = self._get_block(x, y)
io.write(bytes([
ch & 0xFF,
fg << 4 | bg
]))
def export(self, io: TextIO):
io.write("local m = peripheral.find('monitor')\n")
io.write("m.setTextScale(0.5)\n")
io.write(f"-- image: {self._img.width}x{self._img.height}\n")
io.write("\n")
io.write("-- configuring palette\n")
for i in range(16):
r, g, b = self._palette[i * 3 : (i + 1) * 3]
io.write(f"m.setPaletteColor({self.CC_COLORS[i][1]}, 0x{r:02x}{g:02x}{b:02x})\n")
io.write("\n")
io.write("-- writing pixels\n")
for i, y in enumerate(range(0, self._img.height - 2, 3), 1):
s = []
bgs = ""
fgs = ""
io.write(f"m.setCursorPos(1, {i}); ")
for x in range(0, self._img.width - 1, 2):
ch, bg, fg = self._get_block(x, y)
s.append(ch + 0x80)
bgs += self.CC_COLORS[bg][0]
fgs += self.CC_COLORS[fg][0]
io.write("m.blit(string.char(%s), '%s', '%s')\n" % (
str.join(", ", map(str, s)),
fgs,
bgs
))
def main(fp_in, fp_out):
with Image.open(fp_in) as img:
with open(fp_out, "wb") as fp:
Converter(img, (164, 81)).export_binary(fp)
if __name__ == "__main__":
main(argv[1], argv[2])

73
ccpi.lua Normal file
View File

@ -0,0 +1,73 @@
local colors_list = {
colors.white,
colors.orange,
colors.magenta,
colors.lightBlue,
colors.yellow,
colors.lime,
colors.pink,
colors.gray,
colors.lightGray,
colors.cyan,
colors.purple,
colors.blue,
colors.brown,
colors.green,
colors.red,
colors.black
}
local function load(path)
local image = { w = 0, h = 0, scale = 1.0, palette = {}, lines = {} }
local fp, err = io.open(path, "rb")
if not fp then return nil, err end
local magic = fp:read(4)
if magic ~= "CCPI" then
return nil, "Invalid header: expected CCPI got " .. magic
end
image.w, image.h = string.byte(fp:read(1)), string.byte(fp:read(1))
for i = 1, 16 do
image.palette[i] = bit32.lshift(string.byte(fp:read(1)), 16)
image.palette[i] = bit32.bor(image.palette[i], bit32.lshift(string.byte(fp:read(1)), 8))
image.palette[i] = bit32.bor(image.palette[i], string.byte(fp:read(1)))
end
for y = 1, image.h do
local line = { s = "", bg = "", fg = "" }
for x = 1, image.w do
line.s = line.s .. fp:read(1)
local color = string.byte(fp:read(1))
line.bg = line.bg .. string.format("%x", bit32.band(0xF, color))
line.bg = line.bg .. string.format("%x", bit32.band(0xF, bit32.rshift(color, 4)))
end
table.insert(image.lines, line)
end
fp:close()
return image
end
local function draw(img, ox, oy, monitor)
local t = monitor or term.current()
ox = ox or 1
oy = oy or 1
for i = 1, 16 do
t.setPaletteColor(colors_list[i], img.palette[i])
end
for y = 1, img.h do
t.setCursorPos(ox, oy + y - 1)
t.blit(img.lines[y].s, img.lines[y].fg, img.lines[y].bg)
end
end
return {
load = load,
draw = draw
}

BIN
rat.cpi Normal file

Binary file not shown.