# WL-814x 보고용 캡처 묶음 + 합격선 표 이미지 + 외곽선 두께 실측 import os import numpy as np from PIL import Image, ImageDraw RAW = 'Screenshots_WL/WL814x/raw' OUT = 'Screenshots_WL/WL814x' def L(n): return np.asarray(Image.open(RAW + '/' + n + '.png').convert('RGB'), dtype=np.uint8) ma = L('mask_all')[:, :, 0] > 127 ys, xs = np.where(ma) pad = 6 X0, X1 = xs.min() - pad, xs.max() + pad + 1 Y0, Y1 = ys.min() - pad, ys.max() + pad + 1 def crop(n, z): im = Image.open(RAW + '/' + n + '.png').convert('RGB').crop((X0, Y0, X1, Y1)) return im.resize((im.width * z, im.height * z), Image.NEAREST) if z > 1 else im def grid(out, zoom, names, labels=None): cells = [(labels[i] if labels else names[i], crop(n, zoom), crop(n, 1)) for i, n in enumerate(names) if os.path.exists(RAW + '/' + n + '.png')] gap, lab = 10, 20 W = sum(c[1].width for c in cells) + gap * (len(cells) - 1) H = cells[0][1].height + cells[0][2].height + gap + lab img = Image.new('RGB', (W, H), (24, 24, 28)) d = ImageDraw.Draw(img) x = 0 for t, big, small in cells: img.paste(big, (x, lab)) img.paste(small, (x + (big.width - small.width) // 2, lab + big.height + gap)) d.text((x + 2, 4), t, fill=(235, 235, 235)) x += big.width + gap img.save(OUT + '/' + out) print('saved', out, img.size) # ── 외곽선 두께 실측: 실루엣 바깥 1px 띠가 거의 검정인가 def outline_px(name): im = L(name) inner = np.zeros_like(ma) inner[1:-1, 1:-1] = ma[1:-1, 1:-1] & ma[:-2, 1:-1] & ma[2:, 1:-1] & ma[1:-1, :-2] & ma[1:-1, 2:] band1 = ma & ~inner inner2 = np.zeros_like(ma) inner2[2:-2, 2:-2] = inner[2:-2, 2:-2] & inner[1:-3, 2:-2] & inner[3:-1, 2:-2] & inner[2:-2, 1:-3] & inner[2:-2, 3:-1] band2 = inner & ~inner2 def darkfrac(b): px = im[b] lum = 0.299 * px[:, 0] + 0.587 * px[:, 1] + 0.114 * px[:, 2] return 100.0 * (lum < 40).sum() / max(len(px), 1), int(b.sum()) f1, n1 = darkfrac(band1) f2, n2 = darkfrac(band2) return f1, n1, f2, n2 if __name__ == '__main__': grid('iter1.png', 5, ['i1_a_base_now', 'i1_c20p16', 'i1_c40p16', 'i1_c80p16'], ['지금(89.3%/2676)', '덩어리20 팔레트16', '덩어리40 팔레트16', '덩어리80 팔레트16']) grid('iter2.png', 5, ['i1_a_base_now', 'i2_fV1', 'i2_fP32', 'i2_shades2'], ['지금', '얼굴 814v V1(2048)', '얼굴 P32(1텍셀=1px)', '+셰이드2단']) grid('iter3.png', 5, ['i1_a_base_now', 'i3_c20p10_s2', 'i3_c40p16_s2', 'i3_c40p16_s3', 'i3_c80p16_s2'], ['지금', 'c20p10 s2', 'c40p16 s2', 'c40p16 s3', 'c80p16 s2']) grid('iter4.png', 5, ['i1_a_base_now', 'i4_linNos_s3', 'i5_g0none_s3', 'i5_g1soft_s3', 'i5_g2hard_s3'], ['지금', '선형평균', 'sRGB평균 보정없음', '보정 g1soft(채택)', '보정 g2hard']) grid('z_final_compare.png', 4, ['z_off_now', 'q_runtime'], ['지금 (enabled_=0)', '최종 팔레트 UV (enabled_=1)']) for n in ['z_off_now', 'q_runtime']: print(n, '외곽선 1px띠 어두운비율 %.1f%% (%d px) · 2px째 띠 %.1f%% (%d px)' % outline_px(n)) # ── 합격선 표 이미지 rows = [ ('항목', '지금', '최종', '합격선', '판정'), ('서로 다른 색 비율', '89.3 %', '1.7 %', '≤ 25 %', 'PASS'), ('캐릭터 영역 고유색 수', '2,676', '52', '≤ 60', 'PASS'), ('부위(덩어리)당 색 단계', '41', '평균 2.79 (14칸 중 13칸 2~3)', '≤ 4', 'PASS*'), ('눈이 어두운 덩어리로', '10 px', '36 px (눈 1개 7×5)', '36 px 유지 이상', 'PASS'), ('색상(Hue) 오차', '-', '몸통 2.4° 피부 3.7° 머리칼 3.5°', '평균 ≤ 8°', 'PASS'), ('실루엣 외곽선', '1 px', '1 px (814t 설정 무변경)', '유지', 'PASS'), ] cw = [230, 90, 300, 130, 80] rh = 34 W = sum(cw) + 40 H = rh * len(rows) + 70 img = Image.new('RGB', (W, H), (22, 22, 26)) d = ImageDraw.Draw(img) d.text((20, 14), 'WL-814x 팔레트 아틀라스 UV — 합격선 6항목 (WL_ArenaProto · 직교10 · 1080x1920 · 캐릭터 2996px)', fill=(255, 235, 160)) for r, row in enumerate(rows): y = 48 + r * rh bg = (40, 40, 48) if r == 0 else ((30, 30, 36) if r % 2 else (26, 26, 31)) d.rectangle([16, y, W - 16, y + rh - 2], fill=bg) x = 20 for c, cell in enumerate(row): col = (235, 235, 235) if r > 0 and c == 4: col = (130, 230, 150) if r > 0 and c == 2: col = (150, 210, 255) d.text((x + 6, y + 9), str(cell), fill=col) x += cw[c] d.text((20, 48 + len(rows) * rh + 6), '* 단계 7 인 칸 1개 = 얼굴 데칼이 얹힌 머리 피부 칸(데칼은 별도 부위로 잼)', fill=(200, 200, 200)) img.save(OUT + '/z_metrics.png') print('saved z_metrics.png', img.size)