♻ 复用件审阅+量化改色(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 件零报错。
117 lines
5.4 KiB
Python
117 lines
5.4 KiB
Python
# -*- 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())
|