132 lines
6.5 KiB
Python
132 lines
6.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
# 2026-10-01 PD 지시 — "game Icon Institute_anfg_v3-000018 로라를 적용해서 다시" · 트리거 "game icon institute, game icon, ahfg".
|
|
# 사이트 예시(직접 확인): 굵고 양식화된 손그림풍 MMO 아이콘 · 진한 채도 · 두꺼운 형태 · 발광 · 검은 배경.
|
|
# 앞선 결과가 얇고 사실적으로 나온 원인 = 기반 모델(제작자 권장 "游戏图标研究所 3.0" SD1.5 가 이 PC 에 없어 사실풍 difconsistency 로 대체).
|
|
# 여기서는 같은 기반에서 LoRA 를 더 세게 걸고(변형별 강도), 사실풍을 부정어로 누르고, 2배 다시 그리기를 빼서 LoRA 의 그림체를 최대한 살린다.
|
|
# python comfy_gii_style.py <출력폴더> <round 이름> <LoRA 강도> <clip skip(음수)> [rembg 0|1] [시드 수]
|
|
import io, json, os, sys, time, uuid, urllib.request, urllib.parse
|
|
from PIL import Image, ImageDraw
|
|
|
|
HOST = "http://127.0.0.1:8000"
|
|
CKPT = "difconsistencyCheckpoint_v10.safetensors"
|
|
LORA = "game Icon Institute_anfg_v3-000018.safetensors" # PD 가 loras 폴더에 넣어 둔 원래 이름의 파일
|
|
TRIGGER = "game icon institute, game icon, ahfg"
|
|
# 페이지의 부정어 "(easynegative:0.8), black, dark, Low resolution" + 사실풍 누르기
|
|
NEG = "black, dark, low resolution, worst quality, low quality, blurry, photo, photorealistic, realistic, 3d render, thin, text, watermark, signature, cropped, person, hands"
|
|
|
|
SUBJECTS = [
|
|
("sword", "sword"),
|
|
("blunt", "hammer, mace"),
|
|
("bow", "bow"),
|
|
("dagger", "dagger"),
|
|
("staff", "staff, gem"),
|
|
("magic_dagger", "dagger, glowing, gem"),
|
|
("armor", "armor"),
|
|
("belt", "belt"),
|
|
("gloves", "gloves, gauntlets"),
|
|
("boots", "boots"),
|
|
("ring", "ring, gem"),
|
|
("necklace", "necklace, gem"),
|
|
("shield", "shield"),
|
|
("quiver", "quiver, arrows"),
|
|
("spellbook", "book"),
|
|
("throwing", "shuriken"),
|
|
("orb", "orb, crystal, glowing"),
|
|
("offhand_sword", "short sword"),
|
|
]
|
|
|
|
|
|
def post(path, data):
|
|
req = urllib.request.Request(HOST + path, data=json.dumps(data).encode("utf-8"), headers={"Content-Type": "application/json"})
|
|
return json.loads(urllib.request.urlopen(req, timeout=60).read())
|
|
|
|
|
|
def get(path):
|
|
return urllib.request.urlopen(HOST + path, timeout=180).read()
|
|
|
|
|
|
def workflow(prompt, seed, strength, clip_skip, rembg):
|
|
wf = {
|
|
"1": {"class_type": "CheckpointLoaderSimple", "inputs": {"ckpt_name": CKPT}},
|
|
"2": {"class_type": "LoraLoader", "inputs": {"model": ["1", 0], "clip": ["1", 1], "lora_name": LORA, "strength_model": strength, "strength_clip": strength}},
|
|
"3": {"class_type": "CLIPSetLastLayer", "inputs": {"clip": ["2", 1], "stop_at_clip_layer": clip_skip}},
|
|
"4": {"class_type": "CLIPTextEncode", "inputs": {"clip": ["3", 0], "text": prompt}},
|
|
"5": {"class_type": "CLIPTextEncode", "inputs": {"clip": ["3", 0], "text": NEG}},
|
|
"6": {"class_type": "EmptyLatentImage", "inputs": {"width": 512, "height": 512, "batch_size": 1}},
|
|
"7": {"class_type": "KSampler", "inputs": {"model": ["2", 0], "positive": ["4", 0], "negative": ["5", 0], "latent_image": ["6", 0], "seed": seed, "steps": 28, "cfg": 7.0, "sampler_name": "euler_ancestral", "scheduler": "normal", "denoise": 1.0}},
|
|
"8": {"class_type": "VAEDecode", "inputs": {"samples": ["7", 0], "vae": ["1", 2]}},
|
|
}
|
|
if rembg:
|
|
wf["12"] = {"class_type": "InspyrenetRembg", "inputs": {"image": ["8", 0], "torchscript_jit": "default"}}
|
|
wf["9"] = {"class_type": "SaveImage", "inputs": {"images": ["12", 0], "filename_prefix": "gii_style"}}
|
|
else:
|
|
wf["9"] = {"class_type": "SaveImage", "inputs": {"images": ["8", 0], "filename_prefix": "gii_style"}}
|
|
return wf
|
|
|
|
|
|
def run(prompt, seed, strength, clip_skip, rembg):
|
|
cid = str(uuid.uuid4())
|
|
pid = post("/prompt", {"prompt": workflow(prompt, seed, strength, clip_skip, rembg), "client_id": cid})["prompt_id"]
|
|
t0 = time.time()
|
|
while time.time() - t0 < 900:
|
|
hist = json.loads(get("/history/" + pid))
|
|
if pid in hist:
|
|
st = hist[pid].get("status", {})
|
|
if st.get("status_str") == "error":
|
|
raise RuntimeError(json.dumps(st)[:800])
|
|
outs = hist[pid].get("outputs", {})
|
|
if outs:
|
|
for node in outs.values():
|
|
for im in node.get("images", []):
|
|
q = urllib.parse.urlencode({"filename": im["filename"], "subfolder": im.get("subfolder", ""), "type": im.get("type", "output")})
|
|
return Image.open(io.BytesIO(get("/view?" + q))).convert("RGBA")
|
|
time.sleep(1.2)
|
|
raise TimeoutError("comfy timeout")
|
|
|
|
|
|
def checker(size, n=16):
|
|
im = Image.new("RGB", (size, size), (58, 58, 58))
|
|
d = ImageDraw.Draw(im)
|
|
for y in range(0, size, n):
|
|
for x in range(0, size, n):
|
|
if (x // n + y // n) % 2:
|
|
d.rectangle([x, y, x + n - 1, y + n - 1], fill=(74, 74, 74))
|
|
return im
|
|
|
|
|
|
def main():
|
|
out, tag = sys.argv[1], sys.argv[2]
|
|
strength, clip_skip = float(sys.argv[3]), int(sys.argv[4])
|
|
rembg = int(sys.argv[5]) if len(sys.argv) > 5 else 0
|
|
seeds = int(sys.argv[6]) if len(sys.argv) > 6 else 2
|
|
lo = int(sys.argv[7]) if len(sys.argv) > 7 else 0
|
|
hi = int(sys.argv[8]) if len(sys.argv) > 8 else len(SUBJECTS)
|
|
os.makedirs(out, exist_ok=True)
|
|
subs = SUBJECTS[lo:hi]
|
|
cell, pad, label = 200, 4, 16
|
|
cols = 9
|
|
rows = (len(subs) + cols - 1) // cols
|
|
sheet = Image.new("RGB", (cols * (cell + pad) + pad, rows * (seeds * (cell + pad) + label) + pad), (40, 40, 40))
|
|
draw = ImageDraw.Draw(sheet)
|
|
for i, (name, body) in enumerate(subs):
|
|
prompt = "%s, %s, still life, black background" % (TRIGGER, body)
|
|
cx = pad + (i % cols) * (cell + pad)
|
|
cy = pad + (i // cols) * (seeds * (cell + pad) + label)
|
|
draw.text((cx + 3, cy + 2), name, fill=(240, 220, 160))
|
|
for j in range(seeds):
|
|
t0 = time.time()
|
|
im = run(prompt, 5000 + (lo + i) * 17 + j * 307, strength, clip_skip, rembg)
|
|
print("%s %s #%d %.1f초" % (tag, name, j, time.time() - t0)); sys.stdout.flush()
|
|
im.save(os.path.join(out, "%s_%s_%d.png" % (tag, name, j)))
|
|
bg = checker(cell) if rembg else Image.new("RGB", (cell, cell), (0, 0, 0))
|
|
small = im.resize((cell, cell), Image.LANCZOS)
|
|
bg.paste(small, (0, 0), small)
|
|
sheet.paste(bg, (cx, cy + label + j * (cell + pad)))
|
|
path = os.path.join(out, "%s_sheet.png" % tag)
|
|
sheet.save(path)
|
|
print("sheet", path)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|