from pathlib import Path from PIL import Image, ImageDraw, ImageFont ROOT = Path(__file__).resolve().parents[1] SRC = ROOT / "03_Assets" / "Parts" / "Images" OUT = ROOT / "03_Assets" / "Live2D" / "_parts_contact_sheet.png" def main() -> None: files = sorted(p for p in SRC.glob("*.png") if p.name != "haruka_part_master_apose.png") thumb_w, thumb_h = 220, 380 label_h = 34 cols = 4 rows = (len(files) + cols - 1) // cols sheet = Image.new("RGBA", (cols * thumb_w, rows * (thumb_h + label_h)), (32, 32, 32, 255)) draw = ImageDraw.Draw(sheet) font = ImageFont.load_default() for i, path in enumerate(files): img = Image.open(path).convert("RGBA") img.thumbnail((thumb_w - 16, thumb_h - 16), Image.Resampling.LANCZOS) col = i % cols row = i // cols x = col * thumb_w + (thumb_w - img.width) // 2 y = row * (thumb_h + label_h) + 8 checker = Image.new("RGBA", (thumb_w, thumb_h), (255, 255, 255, 255)) cd = ImageDraw.Draw(checker) for yy in range(0, thumb_h, 20): for xx in range(0, thumb_w, 20): if (xx // 20 + yy // 20) % 2: cd.rectangle((xx, yy, xx + 19, yy + 19), fill=(218, 218, 218, 255)) sheet.alpha_composite(checker, (col * thumb_w, row * (thumb_h + label_h))) sheet.alpha_composite(img, (x, y)) draw.text((col * thumb_w + 8, row * (thumb_h + label_h) + thumb_h + 8), path.stem, fill=(255, 255, 255, 255), font=font) OUT.parent.mkdir(parents=True, exist_ok=True) sheet.convert("RGB").save(OUT) print(OUT) if __name__ == "__main__": main()