153 lines
6.6 KiB
Python
153 lines
6.6 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
ASSET_DIR = ROOT / "client" / "dev" / "res" / "custom" / "47"
|
|
SRC = ASSET_DIR / "wulin_juexue_generated_assets" / "source_generated_asset_sheet_magenta_alpha.png"
|
|
OUT = ASSET_DIR / "wulin_juexue_generated_assets" / "transparent_slices"
|
|
PREVIEW = ASSET_DIR / "wulin_juexue_generated_assets" / "reassembled_preview.png"
|
|
CONTACT = OUT / "_contact_sheet.png"
|
|
|
|
|
|
SLICES = [
|
|
("outer_frame", (0, 40, 626, 320)),
|
|
("content_panel_dark", (660, 72, 454, 266)),
|
|
("secret_book_glow", (1175, 30, 300, 320)),
|
|
("green_vertical_tag", (80, 370, 105, 325)),
|
|
("open_manual_pages", (215, 370, 545, 315)),
|
|
("blue_skill_effect_panel", (785, 370, 675, 295)),
|
|
("long_name_plaque", (28, 708, 460, 88)),
|
|
("small_brown_button", (510, 708, 245, 90)),
|
|
("slot_card_blue", (795, 678, 150, 195)),
|
|
("slot_card_gold", (970, 678, 150, 195)),
|
|
("slot_card_locked_a", (1145, 678, 155, 195)),
|
|
("slot_card_locked_b", (1326, 678, 155, 195)),
|
|
("attribute_panel", (42, 838, 390, 148)),
|
|
("cost_bar", (460, 896, 420, 68)),
|
|
("reroll_button", (945, 880, 405, 118)),
|
|
]
|
|
|
|
|
|
def trim(image: Image.Image, pad: int = 2) -> tuple[Image.Image, tuple[int, int, int, int]]:
|
|
bbox = image.getchannel("A").getbbox()
|
|
if not bbox:
|
|
return image, (0, 0, image.width, image.height)
|
|
left = max(0, bbox[0] - pad)
|
|
top = max(0, bbox[1] - pad)
|
|
right = min(image.width, bbox[2] + pad)
|
|
bottom = min(image.height, bbox[3] + pad)
|
|
return image.crop((left, top, right, bottom)), (left, top, right, bottom)
|
|
|
|
|
|
def paste_fit(base: Image.Image, asset: Image.Image, box: tuple[int, int, int, int]) -> None:
|
|
x, y, w, h = box
|
|
scale = min(w / asset.width, h / asset.height)
|
|
resized = asset.resize((max(1, int(asset.width * scale)), max(1, int(asset.height * scale))), Image.Resampling.LANCZOS)
|
|
base.alpha_composite(resized, (x + (w - resized.width) // 2, y + (h - resized.height) // 2))
|
|
|
|
|
|
def draw_text(base: Image.Image) -> None:
|
|
draw = ImageDraw.Draw(base)
|
|
font_path = "C:/Windows/Fonts/NotoSansSC-VF.ttf"
|
|
bold_path = "C:/Windows/Fonts/simhei.ttf"
|
|
regular = ImageFont.truetype(font_path, 14)
|
|
small = ImageFont.truetype(font_path, 12)
|
|
bold = ImageFont.truetype(bold_path, 20)
|
|
button = ImageFont.truetype(bold_path, 28)
|
|
draw.text((89, 128), "佩戴中:武林秘籍", font=regular, fill=(255, 219, 127, 255))
|
|
draw.text((187, 370), "当前槽位", font=regular, fill=(240, 203, 127, 255))
|
|
draw.text((596, 349), "剑气纵横", font=bold, fill=(255, 220, 112, 255), anchor="mm")
|
|
draw.text((548, 395), "基本剑术伤害提升 5%", font=small, fill=(249, 202, 104, 255))
|
|
draw.text((548, 424), "效果类型:增伤", font=small, fill=(249, 202, 104, 255))
|
|
draw.text((556, 461), "灵石 50", font=small, fill=(148, 255, 132, 255))
|
|
draw.text((659, 461), "金币 5000", font=small, fill=(148, 255, 132, 255))
|
|
draw.text((603, 503), "重新洗练", font=button, fill=(255, 226, 139, 255), anchor="mm")
|
|
|
|
|
|
def make_contact(files: list[Path]) -> None:
|
|
thumbs = []
|
|
for path in files:
|
|
image = Image.open(path).convert("RGBA")
|
|
scale = min(1, 180 / image.width, 120 / image.height)
|
|
thumb = image.resize((max(1, int(image.width * scale)), max(1, int(image.height * scale))), Image.Resampling.LANCZOS)
|
|
thumbs.append((path, image.size, thumb))
|
|
|
|
cols, cell = 3, (240, 170)
|
|
rows = (len(thumbs) + cols - 1) // cols
|
|
sheet = Image.new("RGBA", (cols * cell[0], rows * cell[1]), (46, 46, 46, 255))
|
|
draw = ImageDraw.Draw(sheet)
|
|
for yy in range(0, sheet.height, 10):
|
|
for xx in range(0, sheet.width, 10):
|
|
color = (68, 68, 68, 255) if (xx // 10 + yy // 10) % 2 == 0 else (48, 48, 48, 255)
|
|
draw.rectangle((xx, yy, xx + 9, yy + 9), fill=color)
|
|
for index, (path, size, thumb) in enumerate(thumbs):
|
|
col, row = index % cols, index // cols
|
|
ox, oy = col * cell[0], row * cell[1]
|
|
sheet.alpha_composite(thumb, (ox + (cell[0] - thumb.width) // 2, oy + 8))
|
|
draw.text((ox + 8, oy + 132), path.stem, fill=(240, 240, 240, 255))
|
|
draw.text((ox + 8, oy + 150), f"{size[0]}x{size[1]}", fill=(155, 225, 235, 255))
|
|
sheet.convert("RGB").save(CONTACT)
|
|
|
|
|
|
def main() -> None:
|
|
OUT.mkdir(parents=True, exist_ok=True)
|
|
source = Image.open(SRC).convert("RGBA")
|
|
|
|
manifest = []
|
|
outputs = {}
|
|
for name, box in SLICES:
|
|
x, y, w, h = box
|
|
crop = source.crop((x, y, x + w, y + h))
|
|
final, trim_box = trim(crop)
|
|
path = OUT / f"{name}.png"
|
|
final.save(path)
|
|
outputs[name] = final
|
|
manifest.append(
|
|
{
|
|
"name": name,
|
|
"source_box": {"x": x, "y": y, "width": w, "height": h},
|
|
"trim_box": {
|
|
"x": trim_box[0],
|
|
"y": trim_box[1],
|
|
"width": trim_box[2] - trim_box[0],
|
|
"height": trim_box[3] - trim_box[1],
|
|
},
|
|
"output_size": {"width": final.width, "height": final.height},
|
|
"file": path.name,
|
|
}
|
|
)
|
|
|
|
(OUT / "manifest.json").write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
make_contact([OUT / f"{name}.png" for name, _ in SLICES])
|
|
|
|
preview = Image.new("RGBA", (816, 582), (0, 0, 0, 0))
|
|
paste_fit(preview, outputs["outer_frame"], (0, 0, 816, 582))
|
|
paste_fit(preview, outputs["content_panel_dark"], (55, 104, 728, 447))
|
|
paste_fit(preview, outputs["long_name_plaque"], (68, 119, 220, 50))
|
|
paste_fit(preview, outputs["secret_book_glow"], (72, 150, 165, 165))
|
|
paste_fit(preview, outputs["green_vertical_tag"], (258, 132, 44, 112))
|
|
paste_fit(preview, outputs["open_manual_pages"], (320, 119, 150, 260))
|
|
paste_fit(preview, outputs["blue_skill_effect_panel"], (462, 118, 300, 205))
|
|
paste_fit(preview, outputs["long_name_plaque"], (466, 322, 218, 56))
|
|
paste_fit(preview, outputs["attribute_panel"], (466, 374, 218, 60))
|
|
paste_fit(preview, outputs["slot_card_blue"], (58, 392, 98, 146))
|
|
paste_fit(preview, outputs["slot_card_gold"], (165, 392, 98, 146))
|
|
paste_fit(preview, outputs["slot_card_locked_a"], (272, 392, 98, 146))
|
|
paste_fit(preview, outputs["slot_card_locked_b"], (379, 392, 98, 146))
|
|
paste_fit(preview, outputs["cost_bar"], (512, 438, 225, 36))
|
|
paste_fit(preview, outputs["reroll_button"], (510, 472, 185, 62))
|
|
draw_text(preview)
|
|
preview.save(PREVIEW)
|
|
|
|
print(OUT)
|
|
print(PREVIEW)
|
|
print(CONTACT)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|