Public Access
350 lines
11 KiB
Python
350 lines
11 KiB
Python
#!/usr/bin/env python3
|
|||
|
|
"""Pixel art for the KonoSuba pages (components/el512/konosuba.cpp).
|
||
|
|
|
||
|
|
Original 1-bit fan art: '#' is a lit pixel, anything else is dark. Writes
|
||
|
|
components/el512/konosuba_sprites.h and, with --preview, a magnified PNG of all
|
||
|
|
sprites to check them without the panel.
|
||
|
|
|
||
|
|
python tools/konosuba_sprites.py
|
||
|
|
python tools/konosuba_sprites.py --preview sprites.png
|
||
|
|
"""
|
||
|
|
|
||
|
|
import argparse
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
SPRITES = {
|
||
|
|
# Megumin standing, staff in her right hand (viewer's right).
|
||
|
|
"MEGUMIN": [
|
||
|
|
"...............####.........",
|
||
|
|
"..............######........",
|
||
|
|
".............###...##.......",
|
||
|
|
"............####.....#......",
|
||
|
|
"...........#####............",
|
||
|
|
"..........######............",
|
||
|
|
"..........#######...........",
|
||
|
|
".........########...........",
|
||
|
|
".........#########..........",
|
||
|
|
"........##########..........",
|
||
|
|
"........#.........#.........",
|
||
|
|
"........###########.........",
|
||
|
|
"##########################..",
|
||
|
|
".########################...",
|
||
|
|
".....###############........",
|
||
|
|
".....##.#.##..#.#.##........",
|
||
|
|
".....#.............#........",
|
||
|
|
".....#..##....####.#........",
|
||
|
|
".....#..##....####.#........",
|
||
|
|
".....#.........#...#........",
|
||
|
|
".....##.............#.......",
|
||
|
|
"......#.....##.....#........",
|
||
|
|
".....###..........###.......",
|
||
|
|
"....####.##....##.####......",
|
||
|
|
"...######.######.######.....",
|
||
|
|
"..#######..####..#######....",
|
||
|
|
"..######...####...#######...",
|
||
|
|
".#######...#..#...#######...",
|
||
|
|
".######....#..#....######...",
|
||
|
|
".######...#....#...######...",
|
||
|
|
"#######...#....#...#######..",
|
||
|
|
"######...#......#...######..",
|
||
|
|
"######..##########..######..",
|
||
|
|
"#####...#........#...#####..",
|
||
|
|
"#####..#..........#..#####..",
|
||
|
|
"####...############...####..",
|
||
|
|
"........#......#............",
|
||
|
|
"........#......#............",
|
||
|
|
"........#......#............",
|
||
|
|
"........#......#............",
|
||
|
|
".......###....###...........",
|
||
|
|
".......####...####..........",
|
||
|
|
],
|
||
|
|
# Her staff: a gnarled top around a gem.
|
||
|
|
"STAFF": [
|
||
|
|
"..#####..",
|
||
|
|
".#.....#.",
|
||
|
|
"#..###..#",
|
||
|
|
"#.#####.#",
|
||
|
|
"#.#####.#",
|
||
|
|
"#..###..#",
|
||
|
|
".#.....#.",
|
||
|
|
"..#...#..",
|
||
|
|
"...#.#...",
|
||
|
|
"....#....",
|
||
|
|
"....#....",
|
||
|
|
"...##....",
|
||
|
|
"....#....",
|
||
|
|
"....#....",
|
||
|
|
"....#....",
|
||
|
|
"....##...",
|
||
|
|
"....#....",
|
||
|
|
"....#....",
|
||
|
|
"....#....",
|
||
|
|
"....#....",
|
||
|
|
"...##....",
|
||
|
|
"....#....",
|
||
|
|
"....#....",
|
||
|
|
"....#....",
|
||
|
|
"....#....",
|
||
|
|
"....#....",
|
||
|
|
"....##...",
|
||
|
|
"....#....",
|
||
|
|
"....#....",
|
||
|
|
"....#....",
|
||
|
|
"....#....",
|
||
|
|
"....#....",
|
||
|
|
"....#....",
|
||
|
|
"....#....",
|
||
|
|
"....#....",
|
||
|
|
"....#....",
|
||
|
|
"....#....",
|
||
|
|
"....#....",
|
||
|
|
"....#....",
|
||
|
|
"....#....",
|
||
|
|
"....#....",
|
||
|
|
"....#....",
|
||
|
|
],
|
||
|
|
# Megumin after the explosion: face down, hat fallen off, staff dropped.
|
||
|
|
"MEGUMIN_DOWN": [
|
||
|
|
"....###.....................................................",
|
||
|
|
"...#####....................................................",
|
||
|
|
"..######....................................................",
|
||
|
|
"..#######...................................................",
|
||
|
|
".########.##................................................",
|
||
|
|
".#########..##.........#######..............................",
|
||
|
|
"###########...##.....###########............................",
|
||
|
|
"#.........#.....#...##.#.#.#.#.##..#####################....",
|
||
|
|
"###########......#..#.#.#.#.#.#.#.########################..",
|
||
|
|
"...................##############.###########.##########.###",
|
||
|
|
"....................############..#####...........######...##",
|
||
|
|
"......................########....####.............####.....",
|
||
|
|
],
|
||
|
|
# Small party members for the battle window.
|
||
|
|
"KAZUMA": [
|
||
|
|
"....######......",
|
||
|
|
"...########.....",
|
||
|
|
"..##########....",
|
||
|
|
"..#.#.##.#.#....",
|
||
|
|
"..#........#....",
|
||
|
|
"..#..#..#..#....",
|
||
|
|
"..#........#....",
|
||
|
|
"...#..##..#.....",
|
||
|
|
"....######......",
|
||
|
|
"..##########....",
|
||
|
|
".###.####.###...",
|
||
|
|
".##..#..#..##...",
|
||
|
|
".##..#..#..##.#.",
|
||
|
|
".##..####..##.#.",
|
||
|
|
".##..#..#..##.#.",
|
||
|
|
"..#..#..#..#.###",
|
||
|
|
"....#....#....#.",
|
||
|
|
"....#....#......",
|
||
|
|
"...##....##.....",
|
||
|
|
],
|
||
|
|
"AQUA": [
|
||
|
|
".....####.......",
|
||
|
|
"....#....#......",
|
||
|
|
".....####.......",
|
||
|
|
"....######......",
|
||
|
|
"...########.....",
|
||
|
|
"..##########....",
|
||
|
|
"..#........#....",
|
||
|
|
"..#..#..#..#....",
|
||
|
|
"..#........#....",
|
||
|
|
"..##..##..##....",
|
||
|
|
"..###....###....",
|
||
|
|
".####.##.####...",
|
||
|
|
".##..####..##...",
|
||
|
|
".##.#....#.##...",
|
||
|
|
".#.#......#.#...",
|
||
|
|
".#.#......#.#...",
|
||
|
|
"...########.....",
|
||
|
|
"....#....#......",
|
||
|
|
"....#....#......",
|
||
|
|
"...##....##.....",
|
||
|
|
],
|
||
|
|
"MEGUMIN_S": [
|
||
|
|
".......##.......",
|
||
|
|
"......####......",
|
||
|
|
".....######.....",
|
||
|
|
"....########....",
|
||
|
|
"....#......#....",
|
||
|
|
"################",
|
||
|
|
"...##########...",
|
||
|
|
"...#........#...",
|
||
|
|
"...#.#...##.#...",
|
||
|
|
"...#........#...",
|
||
|
|
"....#..##..#....",
|
||
|
|
".....######.....",
|
||
|
|
"...##########...",
|
||
|
|
"..############..",
|
||
|
|
"..###.####.###..",
|
||
|
|
"..##..#..#..##..",
|
||
|
|
"..##..####..##..",
|
||
|
|
"..############..",
|
||
|
|
".....#....#.....",
|
||
|
|
".....#....#.....",
|
||
|
|
"....##....##....",
|
||
|
|
],
|
||
|
|
"DARKNESS": [
|
||
|
|
"....######......",
|
||
|
|
"...########.###.",
|
||
|
|
"..##########.##.",
|
||
|
|
"..#.##..##.#..#.",
|
||
|
|
"..#........#....",
|
||
|
|
"..#..#..#..#....",
|
||
|
|
"..#........#....",
|
||
|
|
"...#..##..#.....",
|
||
|
|
"....######......",
|
||
|
|
"############....",
|
||
|
|
"##.########.##..",
|
||
|
|
"#..#.#..#.#..#..",
|
||
|
|
"...#.####.#.....",
|
||
|
|
"...#.#..#.#.....",
|
||
|
|
"...#.####.#.....",
|
||
|
|
"...########.....",
|
||
|
|
"....#....#......",
|
||
|
|
"....##...##.....",
|
||
|
|
"...###...###....",
|
||
|
|
],
|
||
|
|
# Giant toad, mouth closed and with its tongue out.
|
||
|
|
"TOAD": [
|
||
|
|
"......####............####......",
|
||
|
|
".....#....#..........#....#.....",
|
||
|
|
"....#..##..#........#..##..#....",
|
||
|
|
"....#..##..##########..##..#....",
|
||
|
|
"...##.....#..........#.....##...",
|
||
|
|
"..#..........................#..",
|
||
|
|
".#....#..........#.......#....#.",
|
||
|
|
"#..............................#",
|
||
|
|
"#.....##....#.........##...#...#",
|
||
|
|
"#..............................#",
|
||
|
|
"#..####################........#",
|
||
|
|
"#......................#.......#",
|
||
|
|
".#........#.......#...........#.",
|
||
|
|
"..#..##..................##..#..",
|
||
|
|
"...##..#................#..##...",
|
||
|
|
"..#.....#..............#.....#..",
|
||
|
|
".#..###..#............#..###..#.",
|
||
|
|
"##########............##########",
|
||
|
|
],
|
||
|
|
"TOAD_TONGUE": [
|
||
|
|
"......####............####......",
|
||
|
|
".....#....#..........#....#.....",
|
||
|
|
"....#..##..#........#..##..#....",
|
||
|
|
"....#..##..##########..##..#....",
|
||
|
|
"...##.....#..........#.....##...",
|
||
|
|
"..#..........................#..",
|
||
|
|
".#....#..........#.......#....#.",
|
||
|
|
"#..............................#",
|
||
|
|
"#.....##....#.........##...#...#",
|
||
|
|
"#..............................#",
|
||
|
|
"#..####################........#",
|
||
|
|
"#...#..................#.......#",
|
||
|
|
".#...#....#.......#...........#.",
|
||
|
|
"..#...#..##..............##..#..",
|
||
|
|
"...##..#.##.............#..##...",
|
||
|
|
"..#.....####...........#.....#..",
|
||
|
|
".#..###..##...........#..###..#.",
|
||
|
|
"##########............##########",
|
||
|
|
],
|
||
|
|
# Flying cabbage, wings up and down.
|
||
|
|
"CABBAGE_A": [
|
||
|
|
"#..............#",
|
||
|
|
"##...######...##",
|
||
|
|
".##.#..#...#.##.",
|
||
|
|
"..##.#.#..#.##..",
|
||
|
|
"...#..###...#...",
|
||
|
|
"...#.#.#.#..#...",
|
||
|
|
"....#..#...#....",
|
||
|
|
".....######.....",
|
||
|
|
],
|
||
|
|
"CABBAGE_B": [
|
||
|
|
"................",
|
||
|
|
".....######.....",
|
||
|
|
"....#..#...#....",
|
||
|
|
"...#.#.#..#.#...",
|
||
|
|
"####..###...####",
|
||
|
|
"##.#.#.#.#..#.##",
|
||
|
|
"#...#..#...#...#",
|
||
|
|
".....######.....",
|
||
|
|
],
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def padded(rows):
|
||
|
|
"""Rows padded with dark pixels to the widest one."""
|
||
|
|
w = max(len(r) for r in rows)
|
||
|
|
return [r.ljust(w, ".") for r in rows]
|
||
|
|
|
||
|
|
|
||
|
|
for _name in SPRITES:
|
||
|
|
SPRITES[_name] = padded(SPRITES[_name])
|
||
|
|
|
||
|
|
# Megumin with her visible eye closed (the other is under the eyepatch).
|
||
|
|
SPRITES["MEGUMIN_BLINK"] = list(SPRITES["MEGUMIN"])
|
||
|
|
SPRITES["MEGUMIN_BLINK"][17] = SPRITES["MEGUMIN"][17][:8] + ".." + SPRITES["MEGUMIN"][17][10:]
|
||
|
|
|
||
|
|
|
||
|
|
def write_header(path):
|
||
|
|
out = [
|
||
|
|
"// Pixel art for the KonoSuba pages, generated by tools/konosuba_sprites.py;",
|
||
|
|
"// edit the art there. Original 1-bit fan art, '#' = lit pixel.",
|
||
|
|
"#pragma once",
|
||
|
|
"",
|
||
|
|
"#include <stdint.h>",
|
||
|
|
"",
|
||
|
|
"namespace esphome::el512::konosuba {",
|
||
|
|
"",
|
||
|
|
"struct Sprite {",
|
||
|
|
" uint8_t w, h;",
|
||
|
|
" const char *const *rows;",
|
||
|
|
"};",
|
||
|
|
"",
|
||
|
|
]
|
||
|
|
for name, rows in SPRITES.items():
|
||
|
|
out.append(f"static const char *const {name}_ROWS[] = {{")
|
||
|
|
out += [f' "{r}",' for r in rows]
|
||
|
|
out.append("};")
|
||
|
|
out.append(f"static constexpr Sprite {name}{{{len(rows[0])}, {len(rows)}, {name}_ROWS}};")
|
||
|
|
out.append("")
|
||
|
|
out.append("} // namespace esphome::el512::konosuba")
|
||
|
|
path.write_text("\n".join(out) + "\n", encoding="utf-8")
|
||
|
|
|
||
|
|
|
||
|
|
def write_preview(path, scale=6):
|
||
|
|
from PIL import Image, ImageDraw
|
||
|
|
|
||
|
|
pad = 4
|
||
|
|
names = list(SPRITES)
|
||
|
|
width = sum(len(SPRITES[n][0]) + pad for n in names) * scale
|
||
|
|
height = (max(len(r) for r in SPRITES.values()) + pad) * scale + 14
|
||
|
|
img = Image.new("RGB", (width, height), (20, 12, 0))
|
||
|
|
draw = ImageDraw.Draw(img)
|
||
|
|
x = 0
|
||
|
|
for name in names:
|
||
|
|
rows = SPRITES[name]
|
||
|
|
draw.text((x + 2, 2), name, fill=(120, 80, 0))
|
||
|
|
for y, row in enumerate(rows):
|
||
|
|
for cx, ch in enumerate(row):
|
||
|
|
if ch == "#":
|
||
|
|
x0, y0 = x + cx * scale, 14 + y * scale
|
||
|
|
draw.rectangle([x0, y0, x0 + scale - 2, y0 + scale - 2], fill=(255, 170, 30))
|
||
|
|
x += (len(rows[0]) + pad) * scale
|
||
|
|
img.save(path)
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
root = Path(__file__).resolve().parent.parent
|
||
|
|
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||
|
|
ap.add_argument("--out", type=Path, default=root / "components/el512/konosuba_sprites.h")
|
||
|
|
ap.add_argument("--preview", type=Path, help="also write a magnified PNG of all sprites")
|
||
|
|
args = ap.parse_args()
|
||
|
|
write_header(args.out)
|
||
|
|
print(f"wrote {args.out}")
|
||
|
|
if args.preview:
|
||
|
|
write_preview(args.preview)
|
||
|
|
print(f"wrote {args.preview}")
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|