5c41eb410b
- 新增HUD图标、面板和状态指示器资产 - 实现HUD图标组件和错误边界组件 - 重构顶部状态栏和底部导航栏 - 更新路线面板样式和交互 - 添加HUD资产清单和切片脚本 - 移除未使用的资产文件 - 调整API安全配置和生产环境设置
151 lines
4.6 KiB
Python
151 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
from pathlib import Path
|
|
|
|
from PIL import Image
|
|
|
|
|
|
WHITE_THRESHOLD = 245
|
|
GREEN_KEY = (0, 255, 0)
|
|
|
|
|
|
def is_gutter(sample: list[tuple[int, int, int, int]]) -> bool:
|
|
white_pixels = 0
|
|
opaque_pixels = 0
|
|
|
|
for red, green, blue, alpha in sample:
|
|
if alpha == 0:
|
|
continue
|
|
opaque_pixels += 1
|
|
if red >= WHITE_THRESHOLD and green >= WHITE_THRESHOLD and blue >= WHITE_THRESHOLD:
|
|
white_pixels += 1
|
|
|
|
if opaque_pixels == 0:
|
|
return False
|
|
|
|
return white_pixels / opaque_pixels >= 0.92
|
|
|
|
|
|
def find_segments(mask: list[bool]) -> list[tuple[int, int]]:
|
|
segments: list[tuple[int, int]] = []
|
|
start: int | None = None
|
|
|
|
for index, is_gap in enumerate(mask):
|
|
if not is_gap and start is None:
|
|
start = index
|
|
if is_gap and start is not None:
|
|
segments.append((start, index))
|
|
start = None
|
|
|
|
if start is not None:
|
|
segments.append((start, len(mask)))
|
|
|
|
return segments
|
|
|
|
|
|
def detect_grid(image: Image.Image) -> tuple[list[tuple[int, int]], list[tuple[int, int]]]:
|
|
rgba = image.convert('RGBA')
|
|
width, height = rgba.size
|
|
pixels = rgba.load()
|
|
|
|
column_mask = []
|
|
for x in range(width):
|
|
sample = [pixels[x, y] for y in range(height)]
|
|
column_mask.append(is_gutter(sample))
|
|
|
|
row_mask = []
|
|
for y in range(height):
|
|
sample = [pixels[x, y] for x in range(width)]
|
|
row_mask.append(is_gutter(sample))
|
|
|
|
columns = [segment for segment in find_segments(column_mask) if segment[1] - segment[0] > 48]
|
|
rows = [segment for segment in find_segments(row_mask) if segment[1] - segment[0] > 48]
|
|
|
|
return columns, rows
|
|
|
|
|
|
def remove_chroma(image: Image.Image) -> Image.Image:
|
|
rgba = image.convert('RGBA')
|
|
cleaned = Image.new('RGBA', rgba.size)
|
|
|
|
for x in range(rgba.width):
|
|
for y in range(rgba.height):
|
|
red, green, blue, alpha = rgba.getpixel((x, y))
|
|
if alpha == 0:
|
|
cleaned.putpixel((x, y), (0, 0, 0, 0))
|
|
continue
|
|
|
|
dominant_green = green - max(red, blue)
|
|
color_distance = abs(red - GREEN_KEY[0]) + abs(green - GREEN_KEY[1]) + abs(blue - GREEN_KEY[2])
|
|
|
|
if color_distance <= 60:
|
|
cleaned.putpixel((x, y), (0, 0, 0, 0))
|
|
elif green > 120 and dominant_green > 26:
|
|
despilled_green = min(green, max(red, blue) + 10)
|
|
new_alpha = max(0, min(alpha, 255 - dominant_green * 2))
|
|
if new_alpha <= 12:
|
|
cleaned.putpixel((x, y), (0, 0, 0, 0))
|
|
else:
|
|
cleaned.putpixel((x, y), (red, despilled_green, blue, new_alpha))
|
|
else:
|
|
cleaned.putpixel((x, y), (red, green, blue, alpha))
|
|
|
|
return cleaned
|
|
|
|
|
|
def trim_alpha(image: Image.Image, padding: int = 8) -> Image.Image:
|
|
bbox = image.getbbox()
|
|
if bbox is None:
|
|
return image
|
|
|
|
left = max(0, bbox[0] - padding)
|
|
top = max(0, bbox[1] - padding)
|
|
right = min(image.width, bbox[2] + padding)
|
|
bottom = min(image.height, bbox[3] + padding)
|
|
return image.crop((left, top, right, bottom))
|
|
|
|
|
|
def save_cells(image: Image.Image, names: list[str], output_dir: Path) -> list[str]:
|
|
columns, rows = detect_grid(image)
|
|
expected = len(columns) * len(rows)
|
|
if expected != len(names):
|
|
raise ValueError(
|
|
f'Grid detection found {len(columns)} columns x {len(rows)} rows = {expected} cells, '
|
|
f'but {len(names)} names were provided.'
|
|
)
|
|
|
|
written: list[str] = []
|
|
index = 0
|
|
for row_start, row_end in rows:
|
|
for col_start, col_end in columns:
|
|
cell = image.crop((col_start, row_start, col_end, row_end))
|
|
cleaned = trim_alpha(remove_chroma(cell))
|
|
target = output_dir / f'{names[index]}.png'
|
|
cleaned.save(target)
|
|
written.append(target.name)
|
|
index += 1
|
|
|
|
return written
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description='Slice a HUD atlas with white gutters and green chroma background.')
|
|
parser.add_argument('--input', required=True, type=Path)
|
|
parser.add_argument('--output-dir', required=True, type=Path)
|
|
parser.add_argument('--names', required=True, help='Comma-separated output filenames without extension.')
|
|
args = parser.parse_args()
|
|
|
|
names = [name.strip() for name in args.names.split(',') if name.strip()]
|
|
args.output_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
atlas = Image.open(args.input)
|
|
written = save_cells(atlas, names, args.output_dir)
|
|
print('\n'.join(written))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|