RogueLife/assets-cg/converters.py

167 lines
4.4 KiB
Python
Raw Normal View History

2021-06-02 09:38:59 +02:00
import fxconv
import re
2021-06-09 20:47:39 +02:00
from PIL import Image, ImageChops
2021-06-02 09:38:59 +02:00
def convert(input, output, params, target):
recognized = True
2021-07-16 15:51:32 +02:00
if params["custom-type"] == "level_map":
o = convert_level_map(input, params)
2021-06-09 20:47:39 +02:00
elif params["custom-type"] == "animation":
o = convert_animation(input, params)
2021-06-02 09:38:59 +02:00
else:
recognized = False
if recognized:
fxconv.elf(o, output, "_" + params["name"], **target)
return 0
return 1
2021-07-16 15:51:32 +02:00
def convert_level_map(input, params):
2021-06-02 09:38:59 +02:00
RE_CATEGORY = re.compile(r'^\[([^\]]+)\]$', re.MULTILINE)
with open(input, "r") as fp:
ct = [x.strip() for x in RE_CATEGORY.split(fp.read())]
ct = [x for x in ct if x != ""]
assert(len(ct) % 2 == 0)
ct = { ct[i]: ct[i+1] for i in range(0, len(ct), 2) }
assert(ct.keys() == { "level", "base", "decor" })
# Read tileset
def tileset(desc):
desc = [line.strip().split() for line in desc.split("\n")]
assert(len(line) <= 16 for line in desc)
tiles = dict()
for y, line in enumerate(desc):
for x, chara in enumerate(line):
tiles[chara] = 16 * y + x
return tiles
base = tileset(ct["base"])
decor = tileset(ct["decor"])
level = [line.strip().split() for line in ct["level"].split("\n")]
height = len(level)
width = max(len(line) for line in level)
tiles = bytearray(2 * width * height)
for y, line in enumerate(level):
for x, tile in enumerate(line):
index = 2 * (width * y + x)
tiles[index] = base[tile[0]]
tiles[index+1] = decor[tile[1]]
o = bytes()
o += fxconv.u16(width) + fxconv.u16(height)
o += bytes(tiles)
return o
2021-06-09 20:47:39 +02:00
def convert_animation(input, params):
img = Image.open(input).convert("RGBA")
fsize = [0,0]
center = (0,0)
durations = None
frame_count = None
next_anim = None
# Read parameters
if "frame_duration" in params:
durations = [int(s) for s in params["frame_duration"].split(",")]
else:
durations = []
if "frame_size" in params:
fsize = list(map(int, params["frame_size"].split("x")))
if "frame_height" in params:
fsize[1] = int(params["frame_height"])
elif not fsize[1]:
fsize[1] = img.height
if "frame_width" in params:
fsize[0] = int(params["frame_width"])
elif not fsize[0] and durations:
fsize[0] = img.width // len(durations)
else:
raise fxconv.FxconvError("frame_width, frame_size, and frame_duration"+
" are all unspecified - tell me more!")
if "center" in params:
center = tuple(map(int, params["center"].split(",")))
else:
center = (fsize[0] // 2, fsize[1] // 2)
next_anim = params.get("next")
name = params["name"]
# Read and minimize frames
grid = fxconv.Grid({ "width": fsize[0], "height": fsize[1] })
frame_count = grid.size(img)
bg = Image.new(img.mode, fsize, (0, 0, 0, 0))
frames = []
total_width = 0
total_height = 0
i = 0
for rect in grid.iter(img):
frame = img.crop(rect)
nonzero = ImageChops.difference(frame, bg)
bbox = nonzero.getbbox()
if bbox:
ox, oy = bbox[0], bbox[1]
frame = frame.crop(bbox)
else:
ox, oy = 0, 0
total_width += frame.width
total_height = max(total_height, frame.height)
frames.append((frame, ox, oy))
i += 1
# Create compact spritesheet and data arrays
if total_width > 256:
raise fxconv.FxconvError("uh total_width>256, call Lephe' x_x")
sheet = Image.new("RGBA", (total_width, total_height))
o = fxconv.ObjectData()
sizeof_frame = 16
x = 0
for i, (frame, ox, oy) in enumerate(frames):
o += fxconv.ref(f"{name}_sheet")
o += fxconv.u8(x) + fxconv.u8(0)
o += fxconv.u8(frame.width) + fxconv.u8(frame.height)
o += fxconv.u8(center[0] - ox)
o += fxconv.u8(center[1] - oy)
o += fxconv.u16(durations[i])
if i < len(frames) - 1:
o += fxconv.ref(name, (i+1) * sizeof_frame)
elif next_anim:
o += fxconv.ref(next_anim)
else:
o += fxconv.u32(0)
sheet.paste(frame, (x, 0))
x += frame.width
o += fxconv.sym(f"{name}_sheet")
o += fxconv.convert_bopti_cg(sheet, {
"profile": params.get("profile", "p8"),
})
return o