候选 A·暖夜霓虹手绘烟火(创始人 2026-07-07 拍)样张:日/夜/高峰/雨四场景(深蓝夜幕+暖黄摊灯霓虹+雨夜倒影) +摊主揭蒸笼冒白汽+三顾客全原创无引用+三菜品+UI 摊灯位;创始人眼验拍锁锚开批产,340–380 件资产按此进 M4。 独立锚包不共用山海 r1;招牌乱字(image-01 夜市街道题材系统性伴生物)按产线除字工序处理不升档 B。 成本良率见 良率与成本-批次二.md;工艺发现叠加批次一 7 条(待蒸馏 .agents/);clean-room 市井烟火全原创。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
61 lines
3.2 KiB
Python
61 lines
3.2 KiB
Python
#!/usr/bin/env python3
|
|
# 拼板图(contact sheet):批次样张 → 单张大图,创始人直接打开逐张眼验(复用批次一脚本,加可选格尺寸参数)
|
|
# 用法: python3 contact-sheet.py <shots.json> <images根目录> <game> <输出.png> [列数] [标题] [格宽] [格高]
|
|
# python3 contact-sheet.py --files <输出.png> <标题> <标签=路径>... (任意文件模式)
|
|
# 排序按 shots.json 镜头单原序;每格附镜头 id 标签;中文标题用霞鹜文楷(OFL,scratchpad 依赖)。
|
|
import sys, os, json
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
|
|
FONT_TTF = '/private/tmp/claude-501/-Users-lili-Project-games-development-ai/69248bd4-16ac-4906-aea1-4157edf7b10f/scratchpad/deps/LXGWWenKai-Regular.ttf'
|
|
|
|
def load_font(size):
|
|
try: return ImageFont.truetype(FONT_TTF, size)
|
|
except Exception: return ImageFont.load_default()
|
|
|
|
def make_sheet(items, out_path, cols, title, tile_w=360, tile_h=420):
|
|
label_h, title_h, gap = 30, 56, 8
|
|
rows = (len(items) + cols - 1) // cols
|
|
W = cols * (tile_w + gap) + gap
|
|
H = title_h + rows * (tile_h + label_h + gap) + gap
|
|
sheet = Image.new('RGB', (W, H), (16, 27, 51)) # 墨蓝深空底,配暖夜霓虹批次
|
|
d = ImageDraw.Draw(sheet)
|
|
d.text((gap + 4, 12), title, fill=(246, 184, 75), font=load_font(30))
|
|
f_label = load_font(20)
|
|
for i, (label, path) in enumerate(items):
|
|
r, c = divmod(i, cols)
|
|
x = gap + c * (tile_w + gap)
|
|
y = title_h + r * (tile_h + label_h + gap)
|
|
try:
|
|
im = Image.open(path).convert('RGB')
|
|
except Exception as e:
|
|
d.text((x, y), f'{label}: 打开失败 {e}', fill=(200, 80, 80), font=f_label); continue
|
|
im.thumbnail((tile_w, tile_h))
|
|
ox, oy = x + (tile_w - im.width) // 2, y + (tile_h - im.height) // 2
|
|
sheet.paste(im, (ox, oy))
|
|
d.rectangle([x, y + tile_h, x + tile_w, y + tile_h + label_h], fill=(12, 20, 38))
|
|
d.text((x + 6, y + tile_h + 4), label, fill=(220, 220, 210), font=f_label)
|
|
sheet.save(out_path)
|
|
print(f'拼板完成:{out_path} ({W}x{H}, {len(items)} 格, {os.path.getsize(out_path)//1024}KB)')
|
|
|
|
if __name__ == '__main__':
|
|
if sys.argv[1] == '--files':
|
|
out, title = sys.argv[2], sys.argv[3]
|
|
items = [(kv.split('=', 1)[0], kv.split('=', 1)[1]) for kv in sys.argv[4:]]
|
|
make_sheet(items, out, cols=min(4, max(1, len(items))), title=title)
|
|
else:
|
|
shots_p, img_root, game, out = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
|
|
cols = int(sys.argv[5]) if len(sys.argv) > 5 else 6
|
|
title = sys.argv[6] if len(sys.argv) > 6 else f'风格版批次二 · {game}'
|
|
tile_w = int(sys.argv[7]) if len(sys.argv) > 7 else 360
|
|
tile_h = int(sys.argv[8]) if len(sys.argv) > 8 else 420
|
|
shots = [s for s in json.load(open(shots_p, encoding='utf-8'))['shots'] if s['game'] == game]
|
|
items = []
|
|
for s in shots:
|
|
hit = None
|
|
for ext in ('.jpg', '.jpeg', '.png', '.webp'):
|
|
p = os.path.join(img_root, game, s['id'] + ext)
|
|
if os.path.exists(p): hit = p; break
|
|
if hit: items.append((s['id'], hit))
|
|
else: print(f"警告:缺 {s['id']}")
|
|
make_sheet(items, out, cols=cols, title=title)
|