New and improved converter
This commit is contained in:
parent
bd9d88e12c
commit
c917da78cb
179
cc-pic.py
179
cc-pic.py
|
@ -1,9 +1,10 @@
|
|||
#!/usr/bin/env python3
|
||||
# x-run: python3 % ~/downloads/kanade/6bf3cdae12b75326e3c23af73105e5781e42e94e.jpg n25.cpi
|
||||
|
||||
from typing import BinaryIO, TextIO
|
||||
from PIL import Image
|
||||
from sys import argv
|
||||
from argparse import ArgumentParser, RawTextHelpFormatter
|
||||
from textwrap import dedent
|
||||
|
||||
|
||||
class Converter:
|
||||
CC_COLORS = [
|
||||
|
@ -25,17 +26,15 @@ class Converter:
|
|||
("f", "colors.black"),
|
||||
]
|
||||
|
||||
PIX_BITS = [
|
||||
[ 1, 2 ],
|
||||
[ 4, 8 ],
|
||||
[ 16, 0 ]
|
||||
]
|
||||
PIX_BITS = [[1, 2], [4, 8], [16, 0]]
|
||||
|
||||
MAX_DIFF = (3 ** 0.5) * 255
|
||||
MAX_DIFF = (3**0.5) * 255
|
||||
|
||||
def __init__(self, image: Image.Image):
|
||||
self._img = image.convert("P", palette=Image.ADAPTIVE, colors=16)
|
||||
self._palette: list[int] = self._img.getpalette() # type: ignore
|
||||
self._palette: list[int] = self._img.getpalette() or []
|
||||
if len(self._palette) < 16 * 3:
|
||||
self._palette += [0] * ((16 * 3) - len(self._palette))
|
||||
|
||||
def _brightness(self, i: int) -> float:
|
||||
r, g, b = self._palette[i * 3 : (i + 1) * 3]
|
||||
|
@ -44,7 +43,9 @@ class Converter:
|
|||
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
|
||||
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
|
||||
|
@ -67,7 +68,9 @@ class Converter:
|
|||
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))):
|
||||
if not self._is_darker(
|
||||
dark_i, bri_i, self._img.getpixel((x + ox, y + oy))
|
||||
):
|
||||
out |= bit
|
||||
# bottom right pixel fix?
|
||||
if self._is_darker(dark_i, bri_i, self._img.getpixel((x + 1, y + 2))):
|
||||
|
@ -82,10 +85,7 @@ class Converter:
|
|||
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 + 0x80) & 0xFF,
|
||||
fg << 4 | bg
|
||||
]))
|
||||
io.write(bytes([(ch + 0x80) & 0xFF, fg << 4 | bg]))
|
||||
|
||||
def export(self, io: TextIO):
|
||||
io.write("local m = peripheral.find('monitor')\n")
|
||||
|
@ -95,7 +95,9 @@ class Converter:
|
|||
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(
|
||||
f"m.setPaletteColor({self.CC_COLORS[i][1]}, 0x{r:02x}{g:02x}{b:02x})\n"
|
||||
)
|
||||
io.write("\n")
|
||||
io.write("-- writing pixels\n")
|
||||
|
||||
|
@ -109,23 +111,136 @@ class Converter:
|
|||
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
|
||||
))
|
||||
io.write(
|
||||
"m.blit(string.char(%s), '%s', '%s')\n"
|
||||
% (str.join(", ", map(str, s)), fgs, bgs)
|
||||
)
|
||||
|
||||
|
||||
def main(fp_in, fp_out):
|
||||
size = 143 * 2, 81 * 3 # 286, 243
|
||||
with Image.new("RGB", size) as canv:
|
||||
with Image.open(fp_in) as img:
|
||||
scale = max(img.width / size[0], img.height / size[0])
|
||||
img = img.resize((int(img.width / scale), int(img.height / scale)))
|
||||
canv.paste(img, ((canv.width - img.width) // 2, (canv.height - img.height) // 2))
|
||||
with open(fp_out, "wb") as fp:
|
||||
Converter(canv).export_binary(fp)
|
||||
def main():
|
||||
parser = ArgumentParser(
|
||||
description="ComputerCraft Palette Image converter",
|
||||
formatter_class=RawTextHelpFormatter,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
dest="textmode",
|
||||
action="store_true",
|
||||
help="Output a Lua script instead of binary image",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-W",
|
||||
dest="width",
|
||||
default=4 * 8 - 1,
|
||||
type=int,
|
||||
help="Width in characters",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-H",
|
||||
dest="height",
|
||||
default=3 * 6 - 2,
|
||||
type=int,
|
||||
help="Height in characters",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
dest="placement",
|
||||
choices=("center", "cover", "tile", "full", "extend", "fill"),
|
||||
default="full",
|
||||
help=dedent(
|
||||
"""\
|
||||
Image placement mode (same as in hsetroot)
|
||||
-p center Render image centered on screen
|
||||
-p cover Centered on screen, scaled to fill fully
|
||||
-p tile Render image tiles
|
||||
-p full Maximum aspect ratio
|
||||
-p extend Same as "full" but filling borders
|
||||
-p fill Stretch to fill"""
|
||||
),
|
||||
)
|
||||
parser.add_argument("image_path")
|
||||
parser.add_argument("output_path")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
with Image.new("RGB", (args.width * 2, args.height * 3)) as canv:
|
||||
with Image.open(args.image_path).convert("RGB") as img:
|
||||
if args.placement == "fill":
|
||||
canv.paste(img.resize(canv.size), (0, 0))
|
||||
elif args.placement in ("full", "extend", "cover"):
|
||||
aspect = canv.width / img.width
|
||||
if (img.height * aspect > canv.height) != (
|
||||
args.placement == "cover"
|
||||
):
|
||||
aspect = canv.height / img.height
|
||||
new_w, new_h = int(img.width * aspect), int(
|
||||
img.height * aspect
|
||||
)
|
||||
top = int((canv.height - new_h) / 2)
|
||||
left = int((canv.width - new_w) / 2)
|
||||
resized_img = img.resize((new_w, new_h))
|
||||
canv.paste(resized_img, (left, top))
|
||||
if args.placement == "extend":
|
||||
if left > 0:
|
||||
right = left - 1 + new_w
|
||||
w = 1
|
||||
while right + w < canv.width:
|
||||
canv.paste(
|
||||
canv.crop(
|
||||
(left + 1 - w, 0, left + 1, canv.height)
|
||||
),
|
||||
(left + 1 - w * 2, 0),
|
||||
)
|
||||
canv.paste(
|
||||
canv.crop((right, 0, right + w, canv.height)),
|
||||
(right + w, 0),
|
||||
)
|
||||
w *= 2
|
||||
if top > 0:
|
||||
bottom = top - 1 + new_h
|
||||
h = 1
|
||||
while bottom + h < canv.height:
|
||||
canv.paste(
|
||||
canv.crop(
|
||||
(0, top + 1 - h, canv.width, top + 1)
|
||||
),
|
||||
(top + 1 - h * 2, 0),
|
||||
)
|
||||
canv.paste(
|
||||
canv.crop((0, bottom, canv.width, bottom + h)),
|
||||
(0, bottom + h),
|
||||
)
|
||||
h *= 2
|
||||
|
||||
elif args.placement in ("center", "tile"):
|
||||
left = int((canv.width - img.width) / 2)
|
||||
top = int((canv.height - img.height) / 2)
|
||||
if args.placement == "tile":
|
||||
while left > 0:
|
||||
left -= img.width
|
||||
while top > 0:
|
||||
top -= img.height
|
||||
x = left
|
||||
while x < canv.width:
|
||||
y = top
|
||||
while y < canv.height:
|
||||
canv.paste(img, (x, y))
|
||||
y += img.height
|
||||
x += img.width
|
||||
else:
|
||||
canv.paste(img, (left, top))
|
||||
else:
|
||||
pass
|
||||
|
||||
converter = Converter(canv)
|
||||
converter._img.save("/tmp/_ccpictmp.png")
|
||||
if args.textmode:
|
||||
with open(args.output_path, "w") as fp:
|
||||
converter.export(fp)
|
||||
else:
|
||||
with open(args.output_path, "wb") as fp:
|
||||
converter.export_binary(fp)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(argv[1], argv[2])
|
||||
|
||||
main()
|
||||
|
|
Loading…
Reference in New Issue