Initial Dansori character workspace
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFilter
|
||||
|
||||
|
||||
ROOT = Path(r"D:\Work_AI\Dansori")
|
||||
WORK = ROOT / "Characters_Build_Docs"
|
||||
SRC = WORK / "tmp" / "imagegen" / "leesori_v3_source_alpha.png"
|
||||
APP_ASSETS = ROOT / "DansoriEQ" / "src" / "DansoriEQ.App" / "Assets"
|
||||
PUPPET = APP_ASSETS / "Characters" / "Puppets" / "LeeSoriV3"
|
||||
IMAGES = PUPPET / "Images"
|
||||
PREVIEW = APP_ASSETS / "Characters" / "Live2DPreview" / "leesori.png"
|
||||
|
||||
|
||||
def alpha_bbox(im: Image.Image) -> tuple[int, int, int, int]:
|
||||
return im.getchannel("A").getbbox() or (0, 0, im.width, im.height)
|
||||
|
||||
|
||||
def prepare_source() -> Image.Image:
|
||||
src = Image.open(SRC).convert("RGBA")
|
||||
src = src.crop(alpha_bbox(src))
|
||||
target_h = 1120
|
||||
target_w = int(round(src.width * target_h / src.height))
|
||||
src = src.resize((target_w, target_h), Image.Resampling.LANCZOS)
|
||||
margin = 44
|
||||
out = Image.new("RGBA", (target_w + margin * 2, target_h + margin * 2), (0, 0, 0, 0))
|
||||
out.alpha_composite(src, (margin, margin))
|
||||
return out
|
||||
|
||||
|
||||
def mask_from_polygon(size: tuple[int, int], points: list[tuple[float, float]], blur: float = 1.0) -> Image.Image:
|
||||
w, h = size
|
||||
pts = [(int(x * w), int(y * h)) for x, y in points]
|
||||
mask = Image.new("L", size, 0)
|
||||
ImageDraw.Draw(mask).polygon(pts, fill=255)
|
||||
if blur:
|
||||
mask = mask.filter(ImageFilter.GaussianBlur(blur))
|
||||
return mask
|
||||
|
||||
|
||||
def make_part(src: Image.Image, name: str, points: list[tuple[float, float]], blur: float = 1.0) -> None:
|
||||
mask = mask_from_polygon(src.size, points, blur)
|
||||
part = src.copy()
|
||||
alpha = Image.composite(part.getchannel("A"), Image.new("L", src.size, 0), mask)
|
||||
part.putalpha(alpha)
|
||||
part.save(IMAGES / name)
|
||||
|
||||
|
||||
def composite_black(src: Image.Image, parts: list[str], dest: Path, width: int = 390, height: int = 600) -> None:
|
||||
canvas = Image.new("RGBA", (width, height), (16, 16, 18, 255))
|
||||
scale = min(width * 0.89 / src.width, height * 1.12 / src.height)
|
||||
target = (int(src.width * scale), int(src.height * scale))
|
||||
x = (width - target[0]) // 2
|
||||
y = int(height - target[1] + 58)
|
||||
for part_name in parts:
|
||||
part = Image.open(IMAGES / part_name).convert("RGBA").resize(target, Image.Resampling.LANCZOS)
|
||||
canvas.alpha_composite(part, (x, y))
|
||||
canvas.convert("RGB").save(dest)
|
||||
|
||||
|
||||
def save_preview(src: Image.Image) -> None:
|
||||
preview = Image.new("RGBA", (512, 768), (0, 0, 0, 0))
|
||||
scale = min(512 * 0.82 / src.width, 768 * 1.04 / src.height)
|
||||
target = (int(src.width * scale), int(src.height * scale))
|
||||
resized = src.resize(target, Image.Resampling.LANCZOS)
|
||||
preview.alpha_composite(resized, ((512 - target[0]) // 2, 768 - target[1] + 36))
|
||||
preview.save(PREVIEW)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
IMAGES.mkdir(parents=True, exist_ok=True)
|
||||
src = prepare_source()
|
||||
w, h = src.size
|
||||
|
||||
(PUPPET / "leesori_v3_source.png").parent.mkdir(parents=True, exist_ok=True)
|
||||
src.save(PUPPET / "leesori_v3_source.png")
|
||||
src.save(IMAGES / "leesori_v3_base.png")
|
||||
|
||||
# Tight masks: moving parts avoid the crop-top hem, waist skin, and pelvis.
|
||||
make_part(src, "leesori_v3_head.png", [(0.27, 0.00), (0.73, 0.00), (0.74, 0.19), (0.64, 0.225), (0.36, 0.225), (0.26, 0.19)], blur=0.45)
|
||||
make_part(src, "leesori_v3_chest.png", [(0.27, 0.18), (0.73, 0.18), (0.70, 0.315), (0.30, 0.315)], blur=0.25)
|
||||
make_part(src, "leesori_v3_pelvis.png", [(0.30, 0.38), (0.70, 0.38), (0.73, 0.56), (0.27, 0.56)], blur=0.8)
|
||||
|
||||
make_part(src, "leesori_v3_upperarm_l.png", [(0.15, 0.23), (0.35, 0.22), (0.31, 0.44), (0.14, 0.48), (0.09, 0.36)], blur=0.9)
|
||||
make_part(src, "leesori_v3_forearm_l.png", [(0.12, 0.42), (0.31, 0.40), (0.24, 0.62), (0.05, 0.64)], blur=0.9)
|
||||
make_part(src, "leesori_v3_hand_l.png", [(0.03, 0.58), (0.25, 0.57), (0.22, 0.72), (0.00, 0.72)], blur=0.7)
|
||||
|
||||
make_part(src, "leesori_v3_upperarm_r.png", [(0.65, 0.22), (0.85, 0.23), (0.91, 0.36), (0.86, 0.48), (0.69, 0.44)], blur=0.9)
|
||||
make_part(src, "leesori_v3_forearm_r.png", [(0.69, 0.40), (0.88, 0.42), (0.95, 0.64), (0.76, 0.62)], blur=0.9)
|
||||
make_part(src, "leesori_v3_hand_r.png", [(0.75, 0.57), (0.97, 0.58), (1.00, 0.72), (0.78, 0.72)], blur=0.7)
|
||||
|
||||
make_part(src, "leesori_v3_thigh_l.png", [(0.27, 0.51), (0.50, 0.51), (0.48, 0.76), (0.27, 0.78)], blur=0.8)
|
||||
make_part(src, "leesori_v3_shin_l.png", [(0.27, 0.73), (0.48, 0.73), (0.47, 0.93), (0.30, 0.93)], blur=0.8)
|
||||
make_part(src, "leesori_v3_foot_l.png", [(0.27, 0.90), (0.50, 0.90), (0.49, 1.00), (0.24, 1.00)], blur=0.6)
|
||||
make_part(src, "leesori_v3_thigh_r.png", [(0.50, 0.51), (0.73, 0.51), (0.73, 0.78), (0.52, 0.76)], blur=0.8)
|
||||
make_part(src, "leesori_v3_shin_r.png", [(0.52, 0.73), (0.73, 0.73), (0.70, 0.93), (0.53, 0.93)], blur=0.8)
|
||||
make_part(src, "leesori_v3_foot_r.png", [(0.50, 0.90), (0.73, 0.90), (0.76, 1.00), (0.51, 1.00)], blur=0.6)
|
||||
|
||||
rig = {
|
||||
"name": "LeeSoriV3",
|
||||
"status": "leesori_v3_new_art_full_rig_v1",
|
||||
"canvas": {"width": w, "height": h},
|
||||
"imageBase": "./Images/",
|
||||
"note": "New generated source art with ordinary waist line and restored full limb rig. Moving masks are kept away from crop-top hem and waist skin.",
|
||||
"bones": [
|
||||
{"name": "base", "parent": None, "pivot": [w * 0.50, h * 0.58], "z": 0, "image": "leesori_v3_base.png"},
|
||||
{"name": "pelvis", "parent": "base", "pivot": [w * 0.50, h * 0.48], "z": 2, "image": "leesori_v3_pelvis.png"},
|
||||
{"name": "chest", "parent": "base", "pivot": [w * 0.50, h * 0.30], "z": 3, "image": "leesori_v3_chest.png"},
|
||||
{"name": "upperarm_l", "parent": "chest", "pivot": [w * 0.30, h * 0.25], "z": 4, "image": "leesori_v3_upperarm_l.png"},
|
||||
{"name": "forearm_l", "parent": "upperarm_l", "pivot": [w * 0.20, h * 0.45], "z": 5, "image": "leesori_v3_forearm_l.png"},
|
||||
{"name": "hand_l", "parent": "forearm_l", "pivot": [w * 0.13, h * 0.61], "z": 6, "image": "leesori_v3_hand_l.png"},
|
||||
{"name": "upperarm_r", "parent": "chest", "pivot": [w * 0.70, h * 0.25], "z": 4, "image": "leesori_v3_upperarm_r.png"},
|
||||
{"name": "forearm_r", "parent": "upperarm_r", "pivot": [w * 0.80, h * 0.45], "z": 5, "image": "leesori_v3_forearm_r.png"},
|
||||
{"name": "hand_r", "parent": "forearm_r", "pivot": [w * 0.87, h * 0.61], "z": 6, "image": "leesori_v3_hand_r.png"},
|
||||
{"name": "thigh_l", "parent": "pelvis", "pivot": [w * 0.40, h * 0.54], "z": 2, "image": "leesori_v3_thigh_l.png"},
|
||||
{"name": "shin_l", "parent": "thigh_l", "pivot": [w * 0.39, h * 0.75], "z": 2, "image": "leesori_v3_shin_l.png"},
|
||||
{"name": "foot_l", "parent": "shin_l", "pivot": [w * 0.39, h * 0.92], "z": 2, "image": "leesori_v3_foot_l.png"},
|
||||
{"name": "thigh_r", "parent": "pelvis", "pivot": [w * 0.60, h * 0.54], "z": 2, "image": "leesori_v3_thigh_r.png"},
|
||||
{"name": "shin_r", "parent": "thigh_r", "pivot": [w * 0.61, h * 0.75], "z": 2, "image": "leesori_v3_shin_r.png"},
|
||||
{"name": "foot_r", "parent": "shin_r", "pivot": [w * 0.61, h * 0.92], "z": 2, "image": "leesori_v3_foot_r.png"},
|
||||
{"name": "head", "parent": "chest", "pivot": [w * 0.50, h * 0.16], "z": 10, "image": "leesori_v3_head.png"},
|
||||
],
|
||||
}
|
||||
(PUPPET / "rig.json").write_text(json.dumps(rig, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
save_preview(src)
|
||||
composite_black(src, [bone["image"] for bone in rig["bones"]], PUPPET / "qa_view_390x600.png")
|
||||
black = Image.new("RGBA", src.size, (16, 16, 18, 255))
|
||||
black.alpha_composite(src)
|
||||
black.convert("RGB").save(PUPPET / "qa_source_black.png")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user