"""Offline packing of approved bar/street art. Native runtime remains MASM.""" import json from collections import deque from PIL import Image import pack_arrival_assets as packs from pack_hall_assets import ROOT, SOURCE, extract, normalize def clean_cell(cell): """Discard small disconnected fragments leaking in from neighboring cells.""" alpha = cell.getchannel("A") width, height = cell.size remaining = {y * width + x for y in range(height) for x in range(width) if alpha.getpixel((x, y))} groups = [] while remaining: seed = remaining.pop() pending, group = deque([seed]), [seed] while pending: point = pending.popleft() x, y = point % width, point // width for dx, dy in ((-1, 0), (1, 0), (0, -1), (0, 1)): a, b = x + dx, y + dy neighbor = b * width + a if 0 <= a < width and 0 <= b < height and neighbor in remaining: remaining.remove(neighbor) group.append(neighbor) pending.append(neighbor) groups.append(group) largest = max(map(len, groups)) for group in groups: if len(group) < largest * .08: for point in group: alpha.putpixel((point % width, point // width), 0) cell.putalpha(alpha) return cell.crop(alpha.getbbox()) def main(): packs.OUTPUT = ROOT / "assets/animations/hall" # Use the observed gutters, not the grid requested in the generation prompt. bartender = extract("bartender-v4.png", 4, 6) # The first pose's detached glass and reaching hand are one animation cell. source = packs.solid_alpha(Image.open(SOURCE / "drink-hand-v4.png").convert("RGBA")) xs, ys = [0, 274, 466, 701, 950, 1205, 1388, 1536], [0, 285, 571, 869] drink = [] for top, bottom in zip(ys, ys[1:]): for left, right in zip(xs, xs[1:]): cell = source.crop((left, top, right, bottom)) drink.append(clean_cell(cell)) source = packs.solid_alpha(Image.open(SOURCE / "street-life-v4.png").convert("RGBA")) street = [] ys = [0, 234, 457, 663, 869, 1024] for top, bottom in zip(ys, ys[1:]): for column in range(8): cell = source.crop((column * 192, top, (column + 1) * 192, bottom)) street.append(clean_cell(cell)) frames = { "bartender-v4.bin": normalize(bartender, (256, 256)), "drink-v4.bin": normalize(drink, (256, 256)), "street-life-v4.bin": (normalize(street[:16], (192, 320)) + normalize(street[16:32], (192, 320)) + normalize(street[32:], (192, 320), False)), } manifest = {} for name, items in frames.items(): packs.write_pack(name, items) preview = Image.new("RGBA", (items[0].width * 4, items[0].height * ((len(items) + 3) // 4)), (25, 29, 31, 255)) for index, frame in enumerate(items): preview.alpha_composite(frame, ((index % 4) * frame.width, (index // 4) * frame.height)) preview.save(ROOT / "build/hall" / (name + ".png")) manifest[name] = {"frames": len(items), "size": items[0].size} (packs.OUTPUT / "manifest-v4.json").write_text(json.dumps(manifest, indent=2), encoding="ascii") print(json.dumps(manifest)) if __name__ == "__main__": main()