|
@@ -43,12 +43,7 @@ def run(manifest, out_root, creds=None, log=print):
|
|
|
def transparent_prompt(extra):
|
|
def transparent_prompt(extra):
|
|
|
return ", ".join([
|
|
return ", ".join([
|
|
|
extra,
|
|
extra,
|
|
|
- "no shadow",
|
|
|
|
|
- "no background",
|
|
|
|
|
- "transparent background",
|
|
|
|
|
- "isolated subject",
|
|
|
|
|
- "clean PNG asset",
|
|
|
|
|
- "输出 PNG,背景必须是真实 Alpha 透明通道,不是白底,不是棋盘格。主体居中,边缘干净,适合用于 App、网页和海报叠加",
|
|
|
|
|
|
|
+ "生成纯透明背景 PNG,真实 Alpha 通道,不要棋盘格,不要白底,不要阴影。",
|
|
|
])
|
|
])
|
|
|
|
|
|
|
|
def alpha_report(img):
|
|
def alpha_report(img):
|
|
@@ -59,6 +54,9 @@ def run(manifest, out_root, creds=None, log=print):
|
|
|
ratio = transparent / max(1, img.width * img.height)
|
|
ratio = transparent / max(1, img.width * img.height)
|
|
|
return mn, mx, ratio
|
|
return mn, mx, ratio
|
|
|
|
|
|
|
|
|
|
+ def has_alpha(img):
|
|
|
|
|
+ return alpha_report(img)[0] == 0
|
|
|
|
|
+
|
|
|
def log_alpha(label, img, required):
|
|
def log_alpha(label, img, required):
|
|
|
if not required:
|
|
if not required:
|
|
|
return
|
|
return
|
|
@@ -68,6 +66,27 @@ def run(manifest, out_root, creds=None, log=print):
|
|
|
else:
|
|
else:
|
|
|
log(f"⚠️ [{label}] 模型返回 PNG 但没有透明 Alpha:alpha={mn}-{mx},请重新生成或换支持透明输出的图像模型")
|
|
log(f"⚠️ [{label}] 模型返回 PNG 但没有透明 Alpha:alpha={mn}-{mx},请重新生成或换支持透明输出的图像模型")
|
|
|
|
|
|
|
|
|
|
+ def generate_checked(label, prompt, size, require_alpha):
|
|
|
|
|
+ retry_suffixes = [
|
|
|
|
|
+ "",
|
|
|
|
|
+ "这不是真透明背景。请重新生成:\n背景必须是 Alpha 透明通道,不是白色、灰色或棋盘格。\n去掉所有背景、阴影、光晕和底板,只保留主体,输出 PNG。",
|
|
|
|
|
+ "这不是真透明背景。请重新生成:\n背景必须是 Alpha 透明通道,不是白色、灰色或棋盘格。\n去掉所有背景、阴影、光晕和底板,只保留主体,输出 PNG。",
|
|
|
|
|
+ ]
|
|
|
|
|
+ last = None
|
|
|
|
|
+ for attempt, suffix in enumerate(retry_suffixes, start=1):
|
|
|
|
|
+ attempt_prompt = ", ".join(x for x in [prompt, suffix] if x)
|
|
|
|
|
+ if attempt > 1:
|
|
|
|
|
+ log(f"🔁 [{label}] Alpha 不合格,重新生成透明 PNG(第 {attempt}/{len(retry_suffixes)} 次)…")
|
|
|
|
|
+ img = providers.generate(creds["provider"], attempt_prompt, creds["api_key"],
|
|
|
|
|
+ creds.get("base_url", "https://api.openai.com/v1"),
|
|
|
|
|
+ creds.get("model", "gpt-image-2"),
|
|
|
|
|
+ size)
|
|
|
|
|
+ last = img
|
|
|
|
|
+ log_alpha(label, img, require_alpha)
|
|
|
|
|
+ if not require_alpha or has_alpha(img):
|
|
|
|
|
+ return img
|
|
|
|
|
+ raise RuntimeError(f"模型连续 {len(retry_suffixes)} 次没有返回真实 Alpha 透明通道;请换支持透明输出的图像模型或稍后重试")
|
|
|
|
|
+
|
|
|
# ---- A. 角色(Spine)----
|
|
# ---- A. 角色(Spine)----
|
|
|
for i, c in enumerate(manifest.get("characters", [])):
|
|
for i, c in enumerate(manifest.get("characters", [])):
|
|
|
cid = c.get("id", f"char_{i}")
|
|
cid = c.get("id", f"char_{i}")
|
|
@@ -88,11 +107,9 @@ def run(manifest, out_root, creds=None, log=print):
|
|
|
transparent_prompt("single separated rigging part only, centered, no text, no other body parts")
|
|
transparent_prompt("single separated rigging part only, centered, no text, no other body parts")
|
|
|
] if x)
|
|
] if x)
|
|
|
log(f"🎨 [{cid}/{part_id}] 生成 Boss 拆件…")
|
|
log(f"🎨 [{cid}/{part_id}] 生成 Boss 拆件…")
|
|
|
- pimg = providers.generate(creds["provider"], part_prompt, creds["api_key"],
|
|
|
|
|
- creds.get("base_url", "https://api.openai.com/v1"),
|
|
|
|
|
- creds.get("model", "gpt-image-2"),
|
|
|
|
|
- part.get("size", c.get("size", creds.get("size", "1024x1024"))))
|
|
|
|
|
- log_alpha(f"{cid}/{part_id}", pimg, True)
|
|
|
|
|
|
|
+ pimg = generate_checked(f"{cid}/{part_id}", part_prompt,
|
|
|
|
|
+ part.get("size", c.get("size", creds.get("size", "1024x1024"))),
|
|
|
|
|
+ True)
|
|
|
part_images[part_id] = pimg
|
|
part_images[part_id] = pimg
|
|
|
spine_builder.build_parts_character(cid, part_images, chars_out, anims, parts)
|
|
spine_builder.build_parts_character(cid, part_images, chars_out, anims, parts)
|
|
|
w, h = 1000, 1000
|
|
w, h = 1000, 1000
|
|
@@ -103,11 +120,7 @@ def run(manifest, out_root, creds=None, log=print):
|
|
|
transparent_prompt("single game icon character or slot symbol, centered, full body in frame, no text, not a boss, not a demon lord, not dark armor"),
|
|
transparent_prompt("single game icon character or slot symbol, centered, full body in frame, no text, not a boss, not a demon lord, not dark armor"),
|
|
|
] if x)
|
|
] if x)
|
|
|
log(f"🎨 [{cid}] 生成角色图…")
|
|
log(f"🎨 [{cid}] 生成角色图…")
|
|
|
- img = providers.generate(creds["provider"], full_prompt, creds["api_key"],
|
|
|
|
|
- creds.get("base_url", "https://api.openai.com/v1"),
|
|
|
|
|
- creds.get("model", "gpt-image-2"),
|
|
|
|
|
- c.get("size", creds.get("size", "1024x1024")))
|
|
|
|
|
- log_alpha(cid, img, True)
|
|
|
|
|
|
|
+ img = generate_checked(cid, full_prompt, c.get("size", creds.get("size", "1024x1024")), True)
|
|
|
spine_builder.build_character(cid, img, chars_out, anims)
|
|
spine_builder.build_character(cid, img, chars_out, anims)
|
|
|
w, h = spine_builder.trim_to_content(img).size
|
|
w, h = spine_builder.trim_to_content(img).size
|
|
|
files = [f"characters/{cid}.json", f"characters/{cid}.atlas", f"characters/{cid}.png"]
|
|
files = [f"characters/{cid}.json", f"characters/{cid}.atlas", f"characters/{cid}.png"]
|
|
@@ -140,11 +153,7 @@ def run(manifest, out_root, creds=None, log=print):
|
|
|
else "full-bleed illustration, no text, no UI elements")
|
|
else "full-bleed illustration, no text, no UI elements")
|
|
|
full_prompt = ", ".join(x for x in [a.get("prompt", ""), style if a.get("use_style") else "", extra] if x)
|
|
full_prompt = ", ".join(x for x in [a.get("prompt", ""), style if a.get("use_style") else "", extra] if x)
|
|
|
log(f"🖼 [{aid}] 生成 UI 美术…")
|
|
log(f"🖼 [{aid}] 生成 UI 美术…")
|
|
|
- img = providers.generate(creds["provider"], full_prompt, creds["api_key"],
|
|
|
|
|
- creds.get("base_url", "https://api.openai.com/v1"),
|
|
|
|
|
- creds.get("model", "gpt-image-2"),
|
|
|
|
|
- a.get("size", creds.get("size", "1024x1024")))
|
|
|
|
|
- log_alpha(aid, img, transparent)
|
|
|
|
|
|
|
+ img = generate_checked(aid, full_prompt, a.get("size", creds.get("size", "1024x1024")), transparent)
|
|
|
os.makedirs(ui_art_out, exist_ok=True)
|
|
os.makedirs(ui_art_out, exist_ok=True)
|
|
|
img.save(os.path.join(ui_art_out, f"{aid}.png"))
|
|
img.save(os.path.join(ui_art_out, f"{aid}.png"))
|
|
|
library["ui_art"].append({"id": aid, "file": f"ui_art/{aid}.png",
|
|
library["ui_art"].append({"id": aid, "file": f"ui_art/{aid}.png",
|