"""Offline import of supplied artwork and exact interactive silver-area maps.""" from pathlib import Path from collections import deque import json import struct from PIL import Image, ImageDraw ROOT = Path(__file__).resolve().parents[2] SRC = ROOT / "assets/sprites/scratch" OUT = ROOT / "assets/animations/scratch" SIZE = (540,720) KINDS = (("neon-ru-v3.png",5,15),("ruby-ru-v3.png",6,18),("sea-ru-v3.png",4,12)) def silver_components(image): w,h = image.size pixels = image.load() candidates = set() for y in range(310,610): for x in range(125,525): r,g,b = pixels[x,y][:3] if max(r,g,b)-min(r,g,b) < 22 and 105 < min(r,g,b) and max(r,g,b) < 225: candidates.add((x,y)) components = [] while candidates: start = candidates.pop() queue = deque([start]) body = [start] while queue: x,y = queue.popleft() for point in ((x-1,y),(x+1,y),(x,y-1),(x,y+1)): if point in candidates: candidates.remove(point) queue.append(point) body.append(point) if len(body)>1000: components.append(body) return sorted(components,key=lambda body:(min(y for x,y in body)//25,min(x for x,y in body))) def pack(name,frames): w,h = frames[0].size raw = b"".join(image.tobytes("raw","BGRA") for image in frames) (OUT/name).write_bytes(struct.pack("<4s4I",b"ANM1",w,h,len(frames),len(raw))+raw) def main(): arts,maps,layouts = [],[],[] for filename,winning,players in KINDS: art = Image.open(SRC/filename).convert("RGBA").resize(SIZE,Image.Resampling.LANCZOS) art.putalpha(255) components = silver_components(art) assert len(components)==winning+players,(filename,len(components)) field_map = Image.new("RGBA",SIZE) fields = [] preview = art.copy() draw = ImageDraw.Draw(preview) for i,body in enumerate(components): left,top = min(x for x,y in body),min(y for x,y in body) right,bottom = max(x for x,y in body)+1,max(y for x,y in body)+1 assert right-left<=128 and bottom-top<=96 # Close tiny texture holes without expanding into the decorated frame. rows = {} for x,y in body: rows.setdefault(y,[]).append(x) area = 0 for y,xs in rows.items(): for x in range(min(xs),max(xs)+1): field_map.putpixel((x,y),(0,0,i+1,255)) area += 1 fields.append(dict(x=left,y=top,w=right-left,h=bottom-top,area=area)) draw.rectangle((left,top,right-1,bottom-1),outline="red") draw.text((left+4,top+4),str(i),fill="black") preview.save(ROOT/f"build/scratch/{filename[:-4]}-fields.png") arts.append(art) maps.append(field_map) layouts.append(dict(file=filename,winning=winning,players=players,fields=fields)) pack("portraits-v3.bin",arts) pack("portrait-fields-v3.bin",maps) (OUT/"portrait-layouts-v3.json").write_text(json.dumps(layouts,indent=2),encoding="utf-8") lines = ["; Generated by pack_scratch_portraits.py; source coordinates, not screen coordinates.",".data"] lines += ["scrPortraitWinning3D dd 5,6,4","scrPortraitPlayers3D dd 15,18,12","scrPortraitFields3D label DWORD"] for layout in layouts: fields = layout["fields"] for field in fields: lines.append(" dd "+",".join(str(field[k]) for k in ("x","y","w","h"))) lines.extend([" dd 0,0,0,0"]*(24-len(fields))) lines += [".code",""] (ROOT/"src_graphics/scratch_portrait_fields.inc").write_text("\n".join(lines),encoding="ascii") print("Packed 3 supplied portrait designs, 20/24/16 exact silver masks and source-space geometry") if __name__=="__main__": main()