cd8d566d82
Порт PoP на Sprinter. Текущий PoC — applications/PoP/roomtest/: комната 1 (фон-композиция тайлов) + Kid с управлением на raw-клавиатуре и коллизией с картой. - roomtest — pop_bg (фон), pop_kid (спрайты Kid, column-major флип, seqtbl-анимация), pop_ctrl (порт control() PoP на held-state kbd_raw), pop_map (коллизия seg004/005: бег/стоп у стены, падение/приземление, отскок seq_47, вертикальный прыжок K4.1). MEMORY=small (DATA сразу за CODE, ~23КБ кода не лезет в huge). - toolchain (PoP) — pop_pack_kid/pop_pack_bg/render_room/extract — распаковка res-графики MSDOS в атласы + композиция комнат. - toolchain/ (корень) — make_hdd.sh (быстрый HDD-тест вместо FDD), png_strip.py / room_compose.py (ассет-пайплайн). - docs — PORT_PLAN, KID_PLAN, форматы ресурсов (Apple II / MSDOS / DAT). - bgtest/coltest/poc — ранние PoC (фон, коллизия, первый прототип). .gitignore: build-артефакты applications/*/*/*; исключены внешние референс-репозитории (SDLPoP/mininim/PR/Apple-II — свои git-клоны) и оригинальные game-данные MSDOS/ (копирайт, только для реверса форматов). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
142 lines
6.4 KiB
Python
142 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
png_strip.py — генерик-конвертер произвольного набора PNG-кадров (РАЗНОГО
|
||
размера каждый) в одну raw-ленту + палитру для sprite-движка Sprinter
|
||
(формат getimage/atlas — см. libbgi/include/sprite.h, toolchain/mkatlas.py).
|
||
|
||
Не завязан ни на какой конкретный проект/игру — просто берёт список
|
||
картинок, которые вы сами укажете, и паковает их в ленту с общим
|
||
канвасом (кадр движка обязан быть фиксированного размера).
|
||
|
||
Использование:
|
||
png_strip.py OUT.raw OUT.pal img1.png img2.png ... [опции]
|
||
|
||
Опции:
|
||
--canvas WxH размер канваса кадра (по умолчанию — максимум
|
||
ширины/высоты среди входных картинок)
|
||
--anchor MODE bottom-center (по умолчанию) | top-left | center
|
||
--base-index N с какого индекса палитры начинать свои цвета
|
||
(по умолчанию 16 — под 16 EGA-слотов initgraph)
|
||
--ega добавить в палитру 16 EGA-цветов BGI перед своими
|
||
(по умолчанию включено; --no-ega выключает)
|
||
--seed-pal FILE переиспользовать цвета уже существующей палитры
|
||
(EGA16 + N своих) — свои индексы этой ленты
|
||
продолжаются ПОСЛЕ них (не с --base-index), а
|
||
OUT.pal получится ПОЛНОЙ merged-палитрой
|
||
(ega+seed+свои), готовой как --seed-pal для
|
||
следующего вызова. Так несколько независимо
|
||
собранных лент (напр. разные типы тайлов) можно
|
||
свести в ОДНУ палитру для одновременного показа
|
||
на экране (см. applications/PoP/poc/tools/
|
||
build_room_palette.py).
|
||
|
||
Каждая картинка — источник ОДНОГО кадра ленты (не атлас-сетка;
|
||
для сетки — см. mkatlas.py raw:FWxFH:NXxNY после этого шага, если
|
||
нужно). Прозрачность — альфа < 128 → индекс 0xFF (сентинел движка).
|
||
|
||
Дальше: toolchain/mkatlas.py OUT.atl OUT.raw:CW x CH:1xN
|
||
"""
|
||
import argparse
|
||
import sys
|
||
from PIL import Image
|
||
|
||
EGA = [
|
||
(0, 0, 0), (168, 0, 0), (0, 168, 0), (168, 168, 0),
|
||
(0, 0, 168), (168, 0, 168), (0, 84, 168), (168, 168, 168),
|
||
(84, 84, 84), (255, 84, 84), (84, 255, 84), (255, 255, 84),
|
||
(84, 84, 255), (255, 84, 255), (84, 255, 255), (255, 255, 255),
|
||
]
|
||
|
||
|
||
def die(msg):
|
||
sys.exit(f"png_strip: {msg}")
|
||
|
||
|
||
def main():
|
||
ap = argparse.ArgumentParser(add_help=False)
|
||
ap.add_argument("out_raw")
|
||
ap.add_argument("out_pal")
|
||
ap.add_argument("images", nargs="+")
|
||
ap.add_argument("--canvas", default=None)
|
||
ap.add_argument("--anchor", default="bottom-center",
|
||
choices=["bottom-center", "top-left", "center"])
|
||
ap.add_argument("--base-index", type=int, default=16)
|
||
ap.add_argument("--ega", dest="ega", action="store_true", default=True)
|
||
ap.add_argument("--no-ega", dest="ega", action="store_false")
|
||
ap.add_argument("--seed-pal", default=None)
|
||
args = ap.parse_args()
|
||
|
||
imgs = [Image.open(p).convert("RGBA") for p in args.images]
|
||
|
||
seed_records = []
|
||
if args.seed_pal:
|
||
raw = open(args.seed_pal, "rb").read()
|
||
if len(raw) % 4:
|
||
die(f"--seed-pal {args.seed_pal}: длина не кратна 4")
|
||
n_ega = 16 if args.ega else 0
|
||
for i in range(n_ega, len(raw) // 4):
|
||
b, g, r, _ = raw[i * 4:i * 4 + 4]
|
||
seed_records.append((r, g, b))
|
||
args.base_index = 16 + len(seed_records)
|
||
|
||
if args.canvas:
|
||
cw, ch = (int(v) for v in args.canvas.lower().split("x"))
|
||
else:
|
||
cw = max(im.width for im in imgs)
|
||
ch = max(im.height for im in imgs)
|
||
if cw > 64 or ch > 64:
|
||
die(f"канвас {cw}x{ch} превышает лимит движка 64x64")
|
||
for p, im in zip(args.images, imgs):
|
||
if im.width > cw or im.height > ch:
|
||
die(f"{p}: {im.width}x{im.height} не влезает в канвас {cw}x{ch}")
|
||
|
||
colors = {}
|
||
for i, rgb in enumerate(seed_records):
|
||
colors[rgb] = 16 + i
|
||
|
||
def color_index(r, g, b):
|
||
key = (r, g, b)
|
||
if key not in colors:
|
||
idx = args.base_index + (len(colors) - len(seed_records))
|
||
if idx >= 0xFF:
|
||
die("палитра переполнена (>= 0xFF своих цветов)")
|
||
colors[key] = idx
|
||
return colors[key]
|
||
|
||
data = bytearray()
|
||
for im in imgs:
|
||
if args.anchor == "top-left":
|
||
ox, oy = 0, 0
|
||
elif args.anchor == "center":
|
||
ox, oy = (cw - im.width) // 2, (ch - im.height) // 2
|
||
else: # bottom-center
|
||
ox, oy = (cw - im.width) // 2, ch - im.height
|
||
canvas = [0xFF] * (cw * ch)
|
||
for y in range(im.height):
|
||
for x in range(im.width):
|
||
r, g, b, a = im.getpixel((x, y))
|
||
if a < 128:
|
||
continue
|
||
canvas[(oy + y) * cw + (ox + x)] = color_index(r, g, b)
|
||
data.extend(canvas)
|
||
open(args.out_raw, "wb").write(data)
|
||
|
||
pal = bytearray()
|
||
if args.ega:
|
||
for b, g, r in EGA:
|
||
pal += bytes((b, g, r, 0))
|
||
new_colors = [rgb for rgb in colors if rgb not in seed_records]
|
||
for (r, g, b) in (*seed_records, *new_colors):
|
||
pal += bytes((b, g, r, 0))
|
||
open(args.out_pal, "wb").write(pal)
|
||
|
||
print(f"png_strip: {len(imgs)} кадров, канвас {cw}x{ch}, "
|
||
f"{len(data)} Б -> {args.out_raw}")
|
||
print(f" палитра {len(seed_records)} seed + {len(new_colors)} новых "
|
||
f"своих цветов ({len(pal)} Б) -> {args.out_pal}")
|
||
print(f"дальше: mkatlas.py OUT.atl {args.out_raw}:{cw}x{ch}:1x{len(imgs)}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|