#!/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()