Files
gplan-2/tools/quantize_ui_skin.py
T
DKChan 0ad16747fc feat(ui): T2 框材组 8 件换皮接线批(样张 v2 冻结色板量化)——UiKit 单点工厂+五视图接线
♻ 复用件审阅+量化改色(tools/quantize_ui_skin.py,全 CC0,闸①色板 token 渐变带重映射/闸②token 天然合规/闸③图标四形互异/闸④授权干净):panel_stone/button_stone 两态/backdrop_stone/border_stone/营地图标四件(钥石金+柜台暖白)九件入 game/assets/art/ui/(PNG 不入 git,缺位 load_tex null 回退)。
接线=UiKit 单点:panel_skinned(B 区舞台 tint PANEL_STAGE/C 区主面板/图例面板+墨字随行改色 §7.2)+_apply_button_skin(全站按钮两态皮+墨字,UiKit.button 消费点零改)+frame_skinned(营地仪式石框容器)+icon_rect(营地钥石行/三柜台头图标,缺位纯文本回退)+开始界面石纹底噪底层(ui_assembler,z 序 dim 之上内容之下)。数据框/暗卡/结算回看保持素面或暗底(§7.1 数据素面纪律)。
✦ B 区背景横幅不生图列缺料清单(归 T5 交付)。8 件=面板九宫格/按钮常态/按钮按压/石纹底噪/仪式石框/钥石图标/柜台图标组/B 区横幅(缺料)。轻闸=冒烟 557/0+四探针 PASS(带素材模式)+编辑器导入 9 件零报错。
2026-09-28 04:10:49 +08:00

117 lines
5.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""UI 框材组量化改色脚本(M-1 迭代 2 T2,2026-09-28)。
♻ 复用件 → 复用闸①色板量化:全部产物按明度重映射到 art-bible §4.1/§7.2 token 渐变带,
不保留原包杂色;闸②禁入带=token 色天然合规;闸③灰度可辨=图标四形互异;闸④授权=全 CC0。
源包 = artwork/ui-reuse-2026-09-23/(素材不入 git——先查本仓库,缺位回退主检出 D:/code/g-plan2):
oga-kenney-pixel-ui-750(CC0,OGA) 9-Slice/Ancient 面板/按钮九宫格 48×48
kenney-fantasy-ui-borders(CC0) panel-border 石框 48×48
5-石纹底噪/stone_6.png(CC0,OGA Handpainted stone) 无缝底噪 1200×1200
kenney-board-game-icons(CC0) 64px 图标四件
产物 → game/assets/art/ui/(终稿素材目录,不入 git;消费 = UiKit.load_tex 守卫,缺位程序化回退)。
用法:python tools/quantize_ui_skin.py
"""
from PIL import Image
import os
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
MAIN_ROOT = "D:/code/g-plan2"
SRC = ("artwork", "ui-reuse-2026-09-23")
OUT = ("game", "assets", "art", "ui")
def src_path(*parts):
for base in (ROOT, MAIN_ROOT):
p = os.path.join(base, *SRC, *parts)
if os.path.exists(p):
return p
raise FileNotFoundError("/".join(SRC + parts))
def out_path(name):
p = os.path.join(ROOT, *OUT, name)
os.makedirs(os.path.dirname(p), exist_ok=True)
return p
# §4.1/§7.2 token 渐变带(量化目标值——样张 v2 冻结色板)
RAMP_PANEL = [(120, 106, 88), (226, 210, 182), (243, 234, 214)] # PANEL_BORDER→PANEL_DATA→PLATE 微亮
RAMP_BUTTON = [(179, 154, 120), (201, 175, 131), (245, 234, 211)] # FLOOR→BRICK(掩尘"可按的砖")→PLATE
RAMP_PRESSED = [(120, 106, 88), (179, 154, 120)] # PANEL_BORDER→FLOOR
RAMP_BACKDROP = [(30, 25, 20), (120, 106, 88)] # BG_ROOT→PANEL_BORDER(低对比自然后退 §6.1)
RAMP_BORDER = [(120, 106, 88), (201, 175, 131)] # PANEL_BORDER→BRICK
ICON_GOLD = (240, 168, 40) # 奖励金(钥石)
ICON_WARM = (240, 226, 200) # 暖白(柜台件,暗卡上可读)
def ramp_color(ramp, t):
if t <= 0:
return ramp[0]
if t >= 1:
return ramp[-1]
seg = t * (len(ramp) - 1)
i = min(int(seg), len(ramp) - 2)
k = seg - i
a, b = ramp[i], ramp[i + 1]
return tuple(int(a[j] + (b[j] - a[j]) * k) for j in range(3))
def remap_luma(img, ramp, gamma=1.0):
"""明度→token 渐变带重映射(保留 alpha 与纹理结构——闸①量化)。"""
im = img.convert("RGBA")
px = im.load()
vals = [sum(px[x, y][:3]) / 3.0 for y in range(im.size[1]) for x in range(im.size[0])
if px[x, y][3] > 8]
lo, hi = min(vals), max(vals)
span = max(1.0, hi - lo)
for y in range(im.size[1]):
for x in range(im.size[0]):
r, g, b, a = px[x, y]
if a <= 8:
continue
t = ((sum((r, g, b)) / 3.0) - lo) / span
px[x, y] = ramp_color(ramp, t ** gamma) + (a,)
return im
def recolor_flat(img, rgb):
"""剪影图标单色重着色(保留 alpha 抗锯齿——形状承载识别,闸③)。"""
im = img.convert("RGBA")
px = im.load()
for y in range(im.size[1]):
for x in range(im.size[0]):
r, g, b, a = px[x, y]
if a > 0:
px[x, y] = rgb + (a,)
return im
def main():
jobs = []
# 面板/按钮九宫格(pixel-ui-750 Ancient;texture_margin 取 8px——消费侧同值)
jobs.append(("panel_stone.png", remap_luma(Image.open(src_path("1-UI面板与按钮", "oga-kenney-pixel-ui-750(CC0)", "9-Slice", "Ancient", "brown.png")), RAMP_PANEL)))
jobs.append(("button_stone.png", remap_luma(Image.open(src_path("1-UI面板与按钮", "oga-kenney-pixel-ui-750(CC0)", "9-Slice", "Ancient", "tan.png")), RAMP_BUTTON)))
jobs.append(("button_stone_pressed.png", remap_luma(Image.open(src_path("1-UI面板与按钮", "oga-kenney-pixel-ui-750(CC0)", "9-Slice", "Ancient", "tan_pressed.png")), RAMP_PRESSED)))
# 石框(fantasy-ui-borders)
jobs.append(("border_stone.png", remap_luma(Image.open(src_path("1-UI面板与按钮", "kenney-fantasy-ui-borders(CC0)", "PNG", "Default", "Border", "panel-border-000.png")), RAMP_BORDER)))
# 石纹底噪(seamless 1200→256,暗带低对比——B 区/开始层背景)
stone = Image.open(src_path("5-石纹底噪", "stone_6.png")).convert("RGB").resize((256, 256), Image.LANCZOS)
jobs.append(("backdrop_stone.png", remap_luma(stone, RAMP_BACKDROP, gamma=1.6)))
# 营地图标四件(board-game-icons 64px;钥石=奖励金,柜台件=暖白)
icon_dir = ("3-节点图与营地图标", "kenney-board-game-icons(CC0)", "PNG", "Default (64px)")
jobs.append(("icon_keystone.png", recolor_flat(Image.open(src_path(*(icon_dir + ("suit_diamonds.png",)))), ICON_GOLD)))
jobs.append(("icon_forge.png", recolor_flat(Image.open(src_path(*(icon_dir + ("sword.png",)))), ICON_WARM)))
jobs.append(("icon_workshop.png", recolor_flat(Image.open(src_path(*(icon_dir + ("flask_full.png",)))), ICON_WARM)))
jobs.append(("icon_atlas.png", recolor_flat(Image.open(src_path(*(icon_dir + ("book_open.png",)))), ICON_WARM)))
for name, im in jobs:
im.save(out_path(name))
print("saved:", "/".join(OUT + (name,)))
print("=== 量化改色完成(%d 件,全 CC0;闸①②③④ 见脚本头注) ===" % len(jobs))
return 0
if __name__ == "__main__":
import sys
sys.exit(main())