Spaces:
Running on Zero
Running on Zero
| from __future__ import annotations | |
| from pathlib import Path | |
| from PIL import Image, ImageEnhance, ImageOps | |
| ROOT = Path(__file__).resolve().parents[1] | |
| SOURCE_DIR = ROOT.parent / "ai-native" | |
| ASSET_DIR = ROOT / "src" / "mafia" / "ui" / "assets" | |
| PORTRAITS = ASSET_DIR / "portraits" | |
| REFERENCE = ASSET_DIR / "reference" | |
| PORTRAIT_CROPS = { | |
| "p1-you": (642, 130, 822, 318), | |
| "p2-luna": (900, 160, 1096, 360), | |
| "p3-rook": (995, 336, 1190, 536), | |
| "p4-jett": (795, 486, 990, 690), | |
| "p5-vesper": (496, 492, 700, 700), | |
| "p6-dante": (316, 332, 514, 536), | |
| "p7-selene": (456, 138, 662, 338), | |
| } | |
| def save_portrait(source: Image.Image, name: str, crop: tuple[int, int, int, int]) -> None: | |
| image = source.crop(crop).resize((288, 288), Image.Resampling.LANCZOS).convert("RGBA") | |
| # Preserve the portrait, but mask it into a game-token circle. | |
| mask = Image.new("L", image.size, 0) | |
| mask = ImageOps.fit(mask, image.size) | |
| circle = Image.new("L", image.size, 0) | |
| from PIL import ImageDraw | |
| draw = ImageDraw.Draw(circle) | |
| draw.ellipse((4, 4, image.size[0] - 4, image.size[1] - 4), fill=255) | |
| image.putalpha(circle) | |
| # Slightly lift contrast for small in-game sizes. | |
| rgb = Image.new("RGBA", image.size, (0, 0, 0, 0)) | |
| rgb.alpha_composite(image) | |
| enhanced = ImageEnhance.Contrast(rgb).enhance(1.08) | |
| enhanced.save(PORTRAITS / f"{name}.png") | |
| def main() -> None: | |
| PORTRAITS.mkdir(parents=True, exist_ok=True) | |
| REFERENCE.mkdir(parents=True, exist_ok=True) | |
| ui1 = Image.open(SOURCE_DIR / "ui-1.png").convert("RGB") | |
| ui3 = Image.open(SOURCE_DIR / "ui-3.png").convert("RGB") | |
| ui4 = Image.open(SOURCE_DIR / "ui-4.png").convert("RGB") | |
| ui5 = Image.open(SOURCE_DIR / "ui-5.png").convert("RGB") | |
| for name, crop in PORTRAIT_CROPS.items(): | |
| save_portrait(ui1, name, crop) | |
| # Background and panel references. These are not full screenshots; they are | |
| # cropped atmospheric surfaces used behind code-native controls. | |
| ui1.crop((270, 70, 1310, 830)).resize((1400, 900), Image.Resampling.LANCZOS).save( | |
| REFERENCE / "table-room.png" | |
| ) | |
| ui4.crop((260, 60, 1320, 820)).resize((1400, 900), Image.Resampling.LANCZOS).save( | |
| REFERENCE / "hot-seat-room.png" | |
| ) | |
| ui5.crop((330, 35, 1280, 820)).resize((1400, 900), Image.Resampling.LANCZOS).save( | |
| REFERENCE / "endgame-room.png" | |
| ) | |
| ui3.crop((1090, 525, 1295, 790)).resize((420, 520), Image.Resampling.LANCZOS).save( | |
| REFERENCE / "assistant-panel.png" | |
| ) | |
| if __name__ == "__main__": | |
| main() | |