Files
park-desktop/scripts/parse_layout.py
T

120 lines
4.3 KiB
Python

#!/usr/bin/env python3
"""
按像素精确解析 PNG 平面图 —— 精确颜色分类 + 高分辨率网格化
颜色规则(依据实际采样校准):
W 黑色(0,0,0) = 墙壁
O 灰色(176,176,176) = 办公室
P 蓝色(64,176,240) = 过道
R 绿色(80,176,96) = 休息厅
Y 橙色(240,112,16) = 共享工位区 / 楼道
C 青绿(80,224,192) = 玻璃顶
. 白色(240,240,240) = 空白/底
"""
import zlib, struct, sys, collections
def read_png(path):
with open(path, 'rb') as f:
data = f.read()
assert data[:8] == b'\x89PNG\r\n\x1a\n', 'Not a PNG'
pos = 8
width = height = None
bit_depth = color_type = None
idat = b''
while pos < len(data):
ln = struct.unpack('>I', data[pos:pos+4])[0]
typ = data[pos+4:pos+8]
chunk = data[pos+8:pos+8+ln]
pos += 12 + ln
if typ == b'IHDR':
width, height, bit_depth, color_type, _, _, _ = struct.unpack('>IIBBBBB', chunk)
elif typ == b'IDAT':
idat += chunk
elif typ == b'IEND':
break
assert bit_depth == 8 and color_type in (2, 6) and width and height
raw = zlib.decompress(idat)
channels = 4 if color_type == 6 else 3
bpp = channels
stride = width * bpp
rows = []
off = 0
prev = bytearray(stride)
for y in range(height):
ft = raw[off]; off += 1
line = bytearray(raw[off:off+stride]); off += stride
if ft == 1:
for i in range(bpp, stride): line[i] = (line[i] + line[i-bpp]) & 0xFF
elif ft == 2:
for i in range(stride): line[i] = (line[i] + prev[i]) & 0xFF
elif ft == 3:
for i in range(stride):
a = line[i-bpp] if i >= bpp else 0
line[i] = (line[i] + ((a + prev[i]) >> 1)) & 0xFF
elif ft == 4:
for i in range(stride):
a = line[i-bpp] if i >= bpp else 0
b = prev[i]
c = prev[i-bpp] if i >= bpp else 0
p = a + b - c
pa, pb, pc = abs(p-a), abs(p-b), abs(p-c)
pr = a if (pa <= pb and pa <= pc) else (b if pb <= pc else c)
line[i] = (line[i] + pr) & 0xFF
rows.append(bytes(line))
prev = line
rgba = []
for line in rows:
if channels == 4:
rgba.append([(line[i], line[i+1], line[i+2], line[i+3]) for i in range(0, stride, 4)])
else:
rgba.append([(line[i], line[i+1], line[i+2], 255) for i in range(0, stride, 3)])
return width, height, rgba
# 精确色板(中心色 + 容差)
PALETTE = [
('W', (0, 0, 0), 70), # 黑 = 墙壁
('O', (176, 176, 176), 40), # 灰 = 办公室
('P', (64, 176, 240), 45), # 蓝 = 过道
('R', (80, 176, 96), 40), # 绿 = 休息厅
('Y', (240, 112, 16), 55), # 橙 = 共享工位/楼道
('C', (80, 224, 192), 55), # 青绿 = 玻璃顶
('.', (240, 240, 240), 40), # 白 = 空白
]
def classify(rgb):
r, g, b = rgb[:3]
best, best_d = '?', 1e9
for tag, (cr, cg, cb), tol in PALETTE:
d = abs(r-cr) + abs(g-cg) + abs(b-cb)
if d <= tol * 3 and d < best_d:
best, best_d = tag, d
return best
def main(path, grid_cols=48, grid_rows=28, sub=3):
w, h, rgba = read_png(path)
print(f'PNG: {w}x{h} 网格: {grid_cols}x{grid_rows}')
cw, ch = w / grid_cols, h / grid_rows
grid = []
for gy in range(grid_rows):
row = []
for gx in range(grid_cols):
cx = int((gx + 0.5) * cw)
cy = int((gy + 0.5) * ch)
votes = collections.Counter()
rad_x, rad_y = max(2, int(cw*0.3)), max(2, int(ch*0.3))
for dy in range(-rad_y, rad_y+1, sub):
for dx in range(-rad_x, rad_x+1, sub):
yy, xx = cy+dy, cx+dx
if 0 <= yy < h and 0 <= xx < w:
votes[classify(rgba[yy][xx])] += 1
row.append(votes.most_common(1)[0][0])
grid.append(row)
# 打印
print(' ' + ''.join(str(c % 10) for c in range(grid_cols)))
for gy, row in enumerate(grid):
print(f'{gy:2d} ' + ''.join(row))
return grid
if __name__ == '__main__':
path = sys.argv[1] if len(sys.argv) > 1 else '详细的布局.png'
main(path)