Project_WL/AgentScripts/WL814x3_report.py

63 lines
4.1 KiB
Python
Raw Normal View History

# WL-814x3 — 최종 수치표 + 필수 캡처 4종
import json
import numpy as np
from PIL import Image, ImageDraw
RAW='Screenshots_WL/WL814x/3rd/raw'; OUT='Screenshots_WL/WL814x/3rd'
def L(n): return np.asarray(Image.open(RAW+'/'+n+'.png').convert('RGB'),dtype=np.uint8)
def hue(c):
r,g,b=[float(x) for x in c]; mx,mn=max(r,g,b),min(r,g,b); d=mx-mn
if d<1e-6: return 0.0
if mx==r: h=((g-b)/d)%6
elif mx==g: h=(b-r)/d+2
else: h=(r-g)/d+4
return h*60.0
def sat(c):
mx=max(c); return (mx-min(c))/mx if mx>1e-6 else 0
def dh(a,b): return (b-a+540)%360-180
idm=L('idmap'); maF=L('mask_Face')[:,:,0]>127; ma=(L('mask_all')[:,:,0]>127)&~maF
G=np.array(Image.open('Assets/WL/Look/Character/Textures/WLPalette_FINAL.png')).shape[0]//8
idref=np.array([[9+j*15,240-j*11,40+j*7] for j in range(G*G)],np.float32)
fl=idm.reshape(-1,3).astype(np.float32); d2=((fl[:,None,:]-idref[None,:,:])**2).sum(2)
kid=np.where((d2.min(1).reshape(idm.shape[:2])<400)&(idm.sum(2)>12), d2.argmin(1).reshape(idm.shape[:2]), -1)
tgt={int(k):np.array(v,np.float32) for k,v in json.load(open('AgentScripts/WL814x2_target.json')).items()}
NAME={8:'머리칼(주)',4:'머리칼(밝은단)',12:'머리칼(하이라이트)',2:'흰 튜닉',14:'부츠 흰면',1:'가죽 갑옷',13:'가죽(어두운)',3:'부츠 갈색',15:'붉은 바지',9:'붉은 천',10:'벨트',11:'팔 소매',0:'머리 피부',5:'다리 피부'}
m10=L('mask_all10')[:,:,0]>127
print('%-16s %7s | %-16s %-16s %8s | %-16s %8s'%('부위','px','원본 RGB','2차(보라 앰비언트)','dHue','3차(중성+보정)','dHue'))
ch0=[];ch1=[];w=[]
for j in sorted(NAME):
sel=ma&(kid==j); n=int(sel.sum())
if n<800: continue
o=tgt[j]; a=L('pre_v0_purple')[sel].mean(0); b=L('f_final')[sel].mean(0)
ea=dh(hue(o),hue(a)); eb=dh(hue(o),hue(b))
print('%-16s %7d | %-16s %-16s %+8.1f | %-16s %+8.1f'%(NAME[j],n,tuple(int(x) for x in o),
tuple(int(x) for x in a),ea,tuple(int(x) for x in b),eb))
if sat(o)>0.12: ch0.append(abs(ea)); ch1.append(abs(eb)); w.append(n)
w=np.array(w,float); ch0=np.array(ch0); ch1=np.array(ch1)
print('유채색 가중 dHue: 2차 %.1f°(최대 %.1f) → 3차 %.1f°(최대 %.1f)'%((ch0*w).sum()/w.sum(),ch0.max(),(ch1*w).sum()/w.sum(),ch1.max()))
for v,lbl in [('nc_gray','보정전(중성 앰비언트)'),('s0_now','3차 최종')]:
g=L(v+'_g')[m10]; k=(g[:,0].astype(np.int32)<<16)|(g[:,1].astype(np.int32)<<8)|g[:,2].astype(np.int32)
u=len(np.unique(k)); print('%s 게임 크기: 캐릭터 %d px · 고유색 %d · 서로 다른 색 %.1f %%'%(lbl,m10.sum(),u,100.0*u/m10.sum()))
# 캡처
ys,xs=np.where(ma|maF); x0,x1,y0,y1=xs.min()-24,xs.max()+25,ys.min()-24,ys.max()+25
def crop(n): return Image.open(RAW+'/'+n+'.png').convert('RGB').crop((x0,y0,x1,y1))
def grid(out,items,scale=1.0):
ims=[(t,crop(n)) for t,n in items]
if scale!=1.0: ims=[(t,im.resize((int(im.width*scale),int(im.height*scale)),Image.NEAREST)) for t,im in ims]
W=sum(i.width for _,i in ims)+18*(len(ims)-1); H=ims[0][1].height+26
o=Image.new('RGB',(W,H),(22,22,26)); d=ImageDraw.Draw(o); x=0
for t,im in ims:
o.paste(im,(x,26)); d.text((x+3,7),t,fill=(240,232,190)); x+=im.width+18
o.save(OUT+'/'+out); print('saved',out,o.size)
grid('d_compare.png',[('2차 (보라 앰비언트)','pre_v0_purple'),('3차 최종 (중성 앰비언트 + 보정)','s0_now')])
grid('d_ambient_steps.png',[('보라(기존)','pre_v0_purple'),('중성 회색','pre_v1_gray'),('옅은 청회','v2_cool'),('옅은 난색','v3_warm')],0.72)
def wide(out,items):
ims=[(t,Image.open(RAW+'/'+n+'.png').convert('RGB').resize((405,720))) for t,n in items]
W=sum(i.width for _,i in ims)+16*(len(ims)-1); H=ims[0][1].height+26
o=Image.new('RGB',(W,H),(22,22,26)); d=ImageDraw.Draw(o); x=0
for t,im in ims:
o.paste(im,(x,26)); d.text((x+3,7),t,fill=(240,232,190)); x+=im.width+16
o.save(OUT+'/'+out); print('saved',out,o.size)
wide('d_key_bg.png',[('기존 키라이트','w1_gray'),('키 (1,.95,.90)','w4_gray_keysoft'),('키 순백 (1,1,1)','w5_gray_keywhite')])
Image.open(RAW+'/f_wide.png').convert('RGB').save(OUT+'/d_final_wide.png')
print('saved d_final_wide.png')