IMX6U-Game/tools/pack_tile_atlas.py

111 lines
3.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
将多个独立 tile PNG 按 JSON 配置打包为 tile atlas生成 C++ header。
用法:
python tools/pack_tile_atlas.py config.json output.h
JSON 格式:
{
"tile_size": 32,
"columns": 4,
"tiles": [
{ "id": 0, "file": "empty.png" },
{ "id": 1, "file": "ground_top.png" },
...
]
}
- tile_size: 每个 tile 的像素尺寸(正方形)
- columns: atlas 每行放几个 tile
- tiles: 按 tile_id 排序的列表id 从 0 开始连续
- file: 相对于 config.json 所在目录的 PNG 路径
- 未指定的 tile_id 自动填充透明
"""
import json
import os
import sys
import math
from PIL import Image
def pack(config_path: str, output_path: str):
config_dir = os.path.dirname(os.path.abspath(config_path))
with open(config_path, "r", encoding="utf-8") as f:
cfg = json.load(f)
tile_size = cfg["tile_size"]
columns = cfg["columns"]
tiles = cfg["tiles"]
max_id = max(t["id"] for t in tiles)
tile_count = max_id + 1
rows = (tile_count + columns - 1) // columns
atlas_w = columns * tile_size
atlas_h = rows * tile_size
atlas = Image.new("RGBA", (atlas_w, atlas_h), (0, 0, 0, 0))
tile_map = {t["id"]: t for t in tiles}
for tid in range(tile_count):
col = tid % columns
row = tid // columns
x = col * tile_size
y = row * tile_size
if tid in tile_map:
png_path = os.path.join(config_dir, tile_map[tid]["file"])
img = Image.open(png_path).convert("RGBA")
if img.size != (tile_size, tile_size):
img = img.resize((tile_size, tile_size), Image.NEAREST)
atlas.paste(img, (x, y))
var_name = os.path.splitext(os.path.basename(output_path))[0]
pixels = list(atlas.getdata())
packed = []
for r, g, b, a in pixels:
packed.append(
((r >> 3) << 11)
| ((g >> 3) << 6)
| ((b >> 3) << 1)
| (1 if a else 0)
)
with open(output_path, "w", encoding="utf-8") as f:
f.write("// Auto-generated by tools/pack_tile_atlas.py\n")
f.write(f"#pragma once\n#include <cstdint>\n#include \"Image.h\"\n\n")
f.write(f"static const int32_t {var_name}_width = {atlas_w};\n")
f.write(f"static const int32_t {var_name}_height = {atlas_h};\n")
f.write(f"static const int32_t {var_name}_tile_size = {tile_size};\n")
f.write(f"static const int32_t {var_name}_columns = {columns};\n\n")
f.write(f"static const uint16_t {var_name}_pixels[] = {{\n")
for i in range(0, len(packed), 16):
chunk = packed[i : i + 16]
line = ", ".join(f"0x{p:04X}" for p in chunk)
f.write(f" {line},\n")
f.write("};\n\n")
f.write(f"static const RenderData::Image {var_name}_image({var_name}_pixels, {var_name}_width, {var_name}_height);\n")
print(f"已生成: {output_path}")
print(f" atlas: {atlas_w}x{atlas_h}, {tile_count} tiles ({columns}x{rows})")
print(f" var: {var_name}")
def main():
if len(sys.argv) < 3:
print(f"用法: {sys.argv[0]} config.json output.h")
sys.exit(1)
pack(sys.argv[1], sys.argv[2])
if __name__ == "__main__":
main()