ComputerCraft Paletted Image thingie
This commit is contained in:
parent
89f4685a22
commit
0c79a8017c
|
@ -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])
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue