136 lines
3.6 KiB
Python
136 lines
3.6 KiB
Python
from PIL import Image
|
||
import csv
|
||
import sys
|
||
import os
|
||
|
||
TILE_W = 8
|
||
TILE_H = 8
|
||
TILES_PER_ROW = 24 # because 24 tiles * 8 px = 192 px rows in the map
|
||
|
||
|
||
def load_tileset(path):
|
||
tileset = Image.open(path).convert("RGBA")
|
||
ts_w, ts_h = tileset.size
|
||
|
||
tiles_x = ts_w // TILE_W
|
||
tiles_y = ts_h // TILE_H
|
||
|
||
tiles = []
|
||
for ty in range(tiles_y):
|
||
for tx in range(tiles_x):
|
||
tile = tileset.crop((
|
||
tx * TILE_W,
|
||
ty * TILE_H,
|
||
tx * TILE_W + TILE_W,
|
||
ty * TILE_H + TILE_H
|
||
))
|
||
tiles.append(tile)
|
||
|
||
print(f"Tileset loaded: {len(tiles)} tiles ({tiles_x}×{tiles_y})")
|
||
return tiles
|
||
|
||
|
||
def find_tile_index(block, tiles):
|
||
"""Return index of tile in tiles[] or raise error if not found."""
|
||
for i, t in enumerate(tiles):
|
||
if list(block.getdata()) == list(t.getdata()):
|
||
print(f"Made {i}, {t}")
|
||
return i
|
||
raise ValueError("Tile not found in tileset!")
|
||
|
||
|
||
def build_csv_from_image(src_path, tileset_path, csv_out):
|
||
img = Image.open(src_path).convert("RGBA")
|
||
w, h = img.size
|
||
|
||
tiles = load_tileset(tileset_path)
|
||
|
||
# Must be aligned
|
||
if w % TILE_W != 0 or h % TILE_H != 0:
|
||
raise ValueError("Image dimensions must be multiples of 8.")
|
||
|
||
map_w = w // TILE_W
|
||
map_h = h // TILE_H
|
||
|
||
print(f"Building CSV: {map_w}×{map_h} tiles")
|
||
|
||
rows = []
|
||
tile_indices = []
|
||
|
||
# Scan image in 8×8 blocks
|
||
for ty in range(map_h):
|
||
row_tiles = []
|
||
for tx in range(map_w):
|
||
block = img.crop((
|
||
tx * TILE_W,
|
||
ty * TILE_H,
|
||
tx * TILE_W + TILE_W,
|
||
ty * TILE_H + TILE_H
|
||
))
|
||
|
||
idx = find_tile_index(block, tiles)
|
||
row_tiles.append(idx)
|
||
tile_indices.append(row_tiles)
|
||
|
||
# Output CSV with line breaks after 24 tiles
|
||
with open(csv_out, "w", newline="") as f:
|
||
writer = csv.writer(f)
|
||
for row in tile_indices:
|
||
# 24-tile chunks
|
||
for i in range(0, len(row), TILES_PER_ROW):
|
||
writer.writerow(row[i:i + TILES_PER_ROW])
|
||
|
||
print(f"CSV written to {csv_out}")
|
||
|
||
|
||
def build_png_from_csv(csv_path, tileset_path, out_path):
|
||
tiles = load_tileset(tileset_path)
|
||
|
||
# Read CSV
|
||
tile_rows = []
|
||
with open(csv_path, newline="") as f:
|
||
reader = csv.reader(f)
|
||
for row in reader:
|
||
if row:
|
||
tile_rows.append([int(v) for v in row])
|
||
|
||
height_blocks = len(tile_rows)
|
||
width_blocks = len(tile_rows[0])
|
||
|
||
out_w = width_blocks * TILE_W
|
||
out_h = height_blocks * TILE_H
|
||
out_img = Image.new("RGBA", (out_w, out_h))
|
||
|
||
print(f"Reconstructing {out_w}×{out_h} image")
|
||
|
||
for ty, row in enumerate(tile_rows):
|
||
for tx, tile_index in enumerate(row):
|
||
tile = tiles[tile_index]
|
||
out_img.paste(tile, (tx * TILE_W, ty * TILE_H))
|
||
|
||
out_img.save(out_path)
|
||
print(f"Reconstructed PNG saved to {out_path}")
|
||
|
||
|
||
# -----------------------------------------------------------------------
|
||
# Command-line wrapper
|
||
# -----------------------------------------------------------------------
|
||
if __name__ == "__main__":
|
||
if len(sys.argv) < 5:
|
||
print("Usage:")
|
||
print(" python build_tilemap.py makemap <tileset.png> <image.png> <out.csv>")
|
||
print(" python build_tilemap.py makepng <tileset.png> <in.csv> <out.png>")
|
||
sys.exit(1)
|
||
|
||
mode = sys.argv[1].lower()
|
||
tile = sys.argv[2]
|
||
a = sys.argv[3]
|
||
b = sys.argv[4]
|
||
|
||
if mode == "makemap":
|
||
build_csv_from_image(a, tile, b)
|
||
elif mode == "makepng":
|
||
build_png_from_csv(a, tile, b)
|
||
else:
|
||
print("Unknown mode.")
|