129 lines
4.3 KiB
Python
129 lines
4.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
主角站姿像素画样例生成器 — Dungeon Sweeper
|
|
依据 design/art/art-bible.md:
|
|
- 小人蓝 #2E6E8E 主色(§4.1) / 亮色描边+落影底盘轮廓通道(§3.1)
|
|
- 3 头身候选档(§3.1 两档之一;终稿归 §5 角色批)
|
|
- 窄/直立/对称/凹凸≤2,剑收于剪影内(§3.1/§3.4)
|
|
- 不用红(危险专属)/金(收益专属)/紫赤 300-345° 禁入带(§4.2)
|
|
输出: hero-standing-pose.png (1x) + hero-standing-pose-preview.png (8x 四联测试条)
|
|
"""
|
|
from PIL import Image, ImageOps
|
|
|
|
PAL = {
|
|
'O': (255, 246, 224, 255), # 亮色描边(暖白)
|
|
'b': (85, 144, 175, 255), # 蓝高光(小人蓝同族)
|
|
'B': (46, 110, 142, 255), # 小人蓝 §4.1
|
|
'd': (31, 74, 96, 255), # 蓝暗部(同族)
|
|
'S': (242, 199, 156, 255), # 肤色(暖)
|
|
's': (214, 159, 108, 255), # 肤暗部
|
|
'E': (51, 38, 27, 255), # 数字墨(眼) §4.1
|
|
'T': (138, 90, 52, 255), # 皮革(木色族 §4.4)
|
|
't': (92, 58, 30, 255), # 皮革暗部
|
|
'W': (183, 195, 203, 255), # 剑-钢
|
|
'w': (132, 147, 157, 255), # 剑-钢暗部
|
|
'G': (110, 91, 62, 255), # 落影底盘(暖深)
|
|
}
|
|
|
|
# 左半 16 列(col 0..15),右半水平镜像;-figure 对称剪影
|
|
LEFT = [
|
|
"................", # 0
|
|
"................", # 1
|
|
"................", # 2
|
|
".............OOO", # 3 兜帽顶
|
|
"............OBBB", # 4
|
|
"...........Obbbb", # 5
|
|
"...........ObbBB", # 6
|
|
"...........ObBBB", # 7
|
|
"...........OSSSS", # 8 额/脸顶(加宽脸窗)
|
|
"...........OSSES", # 9
|
|
"...........OSSES", # 10 眼(下移一档更亲和)
|
|
"...........OSSSS", # 11
|
|
"...........OSsss", # 12 下巴暗
|
|
"...........Odddd", # 13 兜帽收底
|
|
"..........Oddddd", # 14 肩起
|
|
"..........OdBBBB", # 15 肩
|
|
"..........OdBbbB", # 16 胸(中高光)
|
|
".........OBBBbbB", # 17 臂现
|
|
".........OdTBbbB", # 18 臂+背带
|
|
".........OdTBbbB", # 19
|
|
".........OdTBbbB", # 20
|
|
".........OdTBbbB", # 21
|
|
".........OdTBbbB", # 22
|
|
".........OdTBbbB", # 23
|
|
".........OdTttTW", # 24 腰带+扣(2px 居中)
|
|
".........OdTttTW", # 25
|
|
".........OdTTTTt", # 26 双手套握柄
|
|
".........OddBBBt", # 27 柄下
|
|
".........OBBOWWW", # 28 剑格
|
|
".........OBBBBBW", # 29 髋/剑身起
|
|
"..........OdBddW", # 30 腿+剑
|
|
"..........OdBddW", # 31
|
|
"..........OdBddW", # 32
|
|
"..........OdBddW", # 33
|
|
"..........OdBddW", # 34
|
|
"..........OdBddW", # 35
|
|
"..........OdBddW", # 36
|
|
"..........OTTttW", # 37 靴
|
|
"..........OTTttW", # 38
|
|
"..........OTTttW", # 39
|
|
"..........OTTttW", # 40
|
|
"..........OttttW", # 41 靴底
|
|
"..............OW", # 42 剑尖
|
|
"................", # 43
|
|
".........GGGGGGG", # 44 落影底盘
|
|
"...........GGGGG", # 45
|
|
"................", # 46
|
|
"................", # 47
|
|
]
|
|
|
|
W, H = 32, 48
|
|
grid = [['.'] * W for _ in range(H)]
|
|
for y, lft in enumerate(LEFT):
|
|
for x, ch in enumerate(lft):
|
|
grid[y][x] = ch
|
|
grid[y][W - 1 - x] = ch
|
|
|
|
# 剑身右侧 1px 暗部(col 16),避免完全对称的死平
|
|
for y in range(29, 42):
|
|
if grid[y][15] == 'W':
|
|
grid[y][16] = 'w'
|
|
|
|
|
|
def render(scale=1):
|
|
img = Image.new('RGBA', (W, H), (0, 0, 0, 0))
|
|
px = img.load()
|
|
for y in range(H):
|
|
for x in range(W):
|
|
c = PAL.get(grid[y][x])
|
|
if c:
|
|
px[x, y] = c
|
|
return img.resize((W * scale, H * scale), Image.NEAREST)
|
|
|
|
|
|
def on_bg(img, color):
|
|
bg = Image.new('RGBA', img.size, color + (255,))
|
|
bg.alpha_composite(img)
|
|
return bg
|
|
|
|
|
|
one_x = render(1)
|
|
one_x.save('hero-standing-pose.png')
|
|
|
|
# 测试条: 掩尘底 / 裸土底 / 暗底 / 灰度(轮廓通道验证)
|
|
x8 = render(8)
|
|
tiles = [
|
|
on_bg(x8, (201, 175, 131)), # 掩尘 #C9AF83
|
|
on_bg(x8, (179, 154, 120)), # 裸土 #B39A78
|
|
on_bg(x8, (46, 42, 38)), # 暗底对照
|
|
ImageOps.grayscale(on_bg(x8, (201, 175, 131))).convert('RGBA'),
|
|
]
|
|
GAP = 24
|
|
strip = Image.new('RGBA',
|
|
(len(tiles) * x8.width + (len(tiles) + 1) * GAP, x8.height + 2 * GAP),
|
|
(250, 244, 230, 255))
|
|
for i, t in enumerate(tiles):
|
|
strip.alpha_composite(t, (GAP + i * (x8.width + GAP), GAP))
|
|
strip.save('hero-standing-pose-preview.png')
|
|
print('done:', one_x.size, '->', strip.size)
|