@playcraft/cli 0.0.40 → 0.0.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -3
- package/dist/atom-plan/validate-atom-plan.js +298 -0
- package/dist/cli-root-help.js +1 -1
- package/dist/commands/3d.js +363 -0
- package/dist/commands/create.js +337 -0
- package/dist/commands/image.js +1337 -43
- package/dist/commands/recommend.js +1 -1
- package/dist/commands/remix.js +213 -0
- package/dist/commands/skills.js +1379 -0
- package/dist/commands/tools-3d.js +473 -0
- package/dist/commands/tools-generation.js +452 -0
- package/dist/commands/tools-project.js +400 -0
- package/dist/commands/tools-research.js +37 -0
- package/dist/commands/tools-research.test.js +216 -0
- package/dist/commands/tools-utils.js +183 -0
- package/dist/commands/tools.js +7 -616
- package/dist/config.js +2 -0
- package/dist/index.js +19 -1
- package/dist/utils/version-checker.js +8 -11
- package/package.json +9 -3
- package/project-template/.claude/agents/designer.md +120 -0
- package/project-template/.claude/agents/developer.md +124 -0
- package/project-template/.claude/agents/pm.md +164 -0
- package/project-template/.claude/agents/refs/README.md +73 -0
- package/project-template/.claude/agents/refs/designer-art-style-catalog.md +533 -0
- package/project-template/.claude/agents/refs/designer-color-audio-recipes.md +153 -0
- package/project-template/.claude/agents/refs/designer-deliverable-spec.md +191 -0
- package/project-template/.claude/agents/refs/designer-dimension-axis.md +27 -0
- package/project-template/.claude/agents/refs/designer-handoff-v2-checklist.md +68 -0
- package/project-template/.claude/agents/refs/designer-master-composite-recipes.md +208 -0
- package/project-template/.claude/agents/refs/designer-style-exploration-flow.md +37 -0
- package/project-template/.claude/agents/refs/developer-dev-handoff.md +109 -0
- package/project-template/.claude/agents/refs/developer-impl-cookbook.md +134 -0
- package/project-template/.claude/agents/refs/developer-phase1-flow.md +136 -0
- package/project-template/.claude/agents/refs/pm-workflow-detail.md +551 -0
- package/project-template/.claude/agents/refs/reviewer-convergence-eval.md +130 -0
- package/project-template/.claude/agents/refs/reviewer-six-dimension-eval.md +6 -0
- package/project-template/.claude/agents/refs/ta-3d-flip-recipe.md +85 -0
- package/project-template/.claude/agents/refs/ta-atlas-deliverable-standard.md +67 -0
- package/project-template/.claude/agents/refs/ta-batch-pipeline-recipes.md +120 -0
- package/project-template/.claude/agents/refs/ta-image-generation-detail.md +356 -0
- package/project-template/.claude/agents/refs/ta-image-ops-reference.md +495 -0
- package/project-template/.claude/agents/refs/ta-pipeline-cookbook.md +1108 -0
- package/project-template/.claude/agents/refs/ta-tools-reference.md +111 -0
- package/project-template/.claude/agents/refs/ta-vfx-preset-catalog.md +365 -0
- package/project-template/.claude/agents/reviewer.md +127 -0
- package/project-template/.claude/agents/technical-artist.md +122 -0
- package/project-template/.claude/hooks/README.md +44 -0
- package/project-template/.claude/hooks/validate-atom-plan.mjs +224 -0
- package/project-template/.claude/hooks/validate-workflow-stop.mjs +343 -0
- package/project-template/.claude/settings.json +36 -0
- package/project-template/.claude/settings.local.json +4 -0
- package/project-template/.claude/skills/playcraft-ad-psychology/SKILL.md +182 -0
- package/project-template/.claude/skills/playcraft-art-style-guide/SKILL.md +123 -0
- package/project-template/.claude/skills/playcraft-asset-state-sheet/SKILL.md +141 -0
- package/project-template/.claude/skills/playcraft-audio-generation/SKILL.md +280 -0
- package/project-template/.claude/skills/playcraft-batch-pipeline/SKILL.md +184 -0
- package/project-template/.claude/skills/playcraft-build-optimizer/SKILL.md +306 -0
- package/project-template/.claude/skills/playcraft-image-generation/SKILL.md +279 -0
- package/project-template/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +123 -0
- package/project-template/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +254 -0
- package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +235 -0
- package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +97 -0
- package/project-template/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +118 -0
- package/project-template/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +137 -0
- package/project-template/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +397 -0
- package/project-template/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +296 -0
- package/project-template/.claude/skills/playcraft-image-ops/SKILL.md +122 -0
- package/project-template/.claude/skills/playcraft-masking/SKILL.md +373 -0
- package/project-template/.claude/skills/playcraft-research/SKILL.md +212 -0
- package/project-template/.claude/skills/playcraft-sprite-generation/SKILL.md +423 -0
- package/project-template/.claude/skills/playcraft-storyboard/SKILL.md +167 -0
- package/project-template/.claude/skills/playcraft-style-qa/SKILL.md +270 -0
- package/project-template/.claude/skills/playcraft-text-rendering/SKILL.md +236 -0
- package/project-template/.claude/skills/playcraft-vfx-animation/SKILL.md +130 -0
- package/project-template/.claude/skills/playcraft-workflow/SKILL.md +485 -0
- package/project-template/.claude/skills/playwright-cli/SKILL.md +390 -0
- package/project-template/.claude/skills/playwright-cli/references/element-attributes.md +23 -0
- package/project-template/.claude/skills/playwright-cli/references/playwright-tests.md +39 -0
- package/project-template/.claude/skills/playwright-cli/references/request-mocking.md +87 -0
- package/project-template/.claude/skills/playwright-cli/references/running-code.md +240 -0
- package/project-template/.claude/skills/playwright-cli/references/session-management.md +226 -0
- package/project-template/.claude/skills/playwright-cli/references/spec-driven-testing.md +312 -0
- package/project-template/.claude/skills/playwright-cli/references/storage-state.md +275 -0
- package/project-template/.claude/skills/playwright-cli/references/test-generation.md +138 -0
- package/project-template/.claude/skills/playwright-cli/references/tracing.md +142 -0
- package/project-template/.claude/skills/playwright-cli/references/video-recording.md +157 -0
- package/project-template/.cursor/hooks.json +17 -0
- package/project-template/.cursor/rules/playcraft-orchestrator.mdc +137 -0
- package/project-template/.cursor/rules/playcraft-subagent-boundary.mdc +18 -0
- package/project-template/CLAUDE.md +280 -0
- package/project-template/assets/audio/bgm/.gitkeep +0 -0
- package/project-template/assets/audio/sfx/.gitkeep +0 -0
- package/project-template/assets/bundles/.gitkeep +0 -0
- package/project-template/assets/images/bg/.gitkeep +0 -0
- package/project-template/assets/images/reference/.gitkeep +0 -0
- package/project-template/assets/images/storyboard/.gitkeep +0 -0
- package/project-template/assets/images/tiles/.gitkeep +0 -0
- package/project-template/assets/images/ui/.gitkeep +0 -0
- package/project-template/assets/images/vfx/.gitkeep +0 -0
- package/project-template/assets/models/.gitkeep +0 -0
- package/project-template/docs/team/agent-conduct.md +121 -0
- package/project-template/docs/team/agent-runtime-matrix.md +62 -0
- package/project-template/docs/team/atom-plan-format.md +105 -0
- package/project-template/docs/team/collaboration.md +297 -0
- package/project-template/docs/team/core-model.md +50 -0
- package/project-template/docs/team/platform-capabilities.md +15 -0
- package/project-template/docs/team/workflow-changelog.md +65 -0
- package/project-template/docs/team/workflow-consistency-checklist.md +140 -0
- package/project-template/game/config/.gitkeep +0 -0
- package/project-template/game/gameplay/.gitkeep +0 -0
- package/project-template/game/scenes/.gitkeep +0 -0
- package/project-template/logs/.gitkeep +0 -0
- package/project-template/ta-workspace/logs/.gitkeep +0 -0
- package/project-template/ta-workspace/scripts/.gitkeep +0 -0
- package/project-template/ta-workspace/tmp/.gitkeep +0 -0
- package/project-template/templates/atom-plan.template.json +26 -0
- package/project-template/templates/atom-plan.template.md +108 -0
- package/project-template/templates/design-brief.template.md +195 -0
- package/project-template/templates/design-lens-checklist.reference.md +117 -0
- package/project-template/templates/design-methodology.md +99 -0
- package/project-template/templates/designer-log.template.md +114 -0
- package/project-template/templates/developer-log.template.md +134 -0
- package/project-template/templates/five-axis-framework.md +186 -0
- package/project-template/templates/intent-clarifications.template.md +58 -0
- package/project-template/templates/layout-spec.template.md +146 -0
- package/project-template/templates/project-state.template.md +237 -0
- package/project-template/templates/review-report.template.md +91 -0
- package/project-template/templates/style-exploration.template.md +93 -0
- package/project-template/templates/ta-log.template.md +343 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: playcraft-image-generation
|
|
3
|
+
description: PlayCraft AI 图片生成核心决策指南。涵盖模型选择决策树、核心命令模板、背景处理决策树(绿幕策略)、脚本决策规则。精灵图直出/Edit 模型/错误恢复/批量实践等进阶内容见 refs/ta-image-generation-detail.md。
|
|
4
|
+
triggers: 生成图片,generate image,选模型,model selection,reference image,参考图,风格一致,图生图,文生图,AI生图,图片质量,模型选择,500错误,模型失败,generate-image,list-image-models,ta-workspace,batch,批量,脚本,nodejs,edit模型,变体,绿幕,去背景,透明PNG,prompt,提示词,prompt套路,提示词技巧
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# PlayCraft AI 图片生成指南
|
|
8
|
+
|
|
9
|
+
## 0. 开始前必读
|
|
10
|
+
|
|
11
|
+
生图前**必须先运行模型发现命令**:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
playcraft tools list-image-models
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
输出示例:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
MODEL CAPABILITY ALPHA PROVIDERS
|
|
21
|
+
gpt-image-2 text+image no 3 (iegg-litellm, mulerouter, 302)
|
|
22
|
+
gemini-3.1-flash-image-preview text+image no 2 (google, iegg-litellm)
|
|
23
|
+
hy-image-v3.0 text+image no 1 (tencent-cloud)
|
|
24
|
+
flux-kontext-pro text+image no 1 (302)
|
|
25
|
+
qwen-image-edit-max image→image no 1 (mulerouter)
|
|
26
|
+
...
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**列说明**:
|
|
30
|
+
|
|
31
|
+
- `CAPABILITY`:`text→image` 仅文生图 / `image→image` 仅图生图 / `text+image` 两者均支持
|
|
32
|
+
- `ALPHA`:`yes` = 原生透明 PNG;`no` = 需使用绿幕策略(见第 3 节)
|
|
33
|
+
- `PROVIDERS`:支持该模型的 provider 数量,多个 provider 时后端按优先级自动 fallback(顺序尝试,首个成功即返回)
|
|
34
|
+
|
|
35
|
+
**Prompt 套路速查**:[reference/prompt-cookbook.md](reference/prompt-cookbook.md)
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 1. 模型选择决策树
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
需要生图?
|
|
43
|
+
├─ 追求最高质量(概念图、精品单图)?
|
|
44
|
+
│ └─ gpt-image-2(3 个 provider 自动 fallback:iegg-litellm → mulerouter → 302)
|
|
45
|
+
│
|
|
46
|
+
├─ 需要 --reference-image(风格一致性批量生产)?
|
|
47
|
+
│ └─ 必须选 CAPABILITY = text+image 或 image→image 的模型
|
|
48
|
+
│
|
|
49
|
+
├─ 同类元素批量生成(<=16 个)?
|
|
50
|
+
│ └─ 精灵图直出策略(见进阶参考)
|
|
51
|
+
│
|
|
52
|
+
├─ 结构相似的变体集(换花色/换数字/换颜色)?
|
|
53
|
+
│ └─ Edit 模型工作流(见进阶参考)
|
|
54
|
+
│
|
|
55
|
+
└─ 不确定?
|
|
56
|
+
└─ 先试 gpt-image-2,失败换 gemini-3.1-flash-image-preview
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 主要模型特性
|
|
60
|
+
|
|
61
|
+
| 模型 | 特点与优势 | 最佳场景 | 速度 | 注意事项 |
|
|
62
|
+
| ---------------------------------- | ------------------------------------------------------------------------------------------- | ----------------------------------------------- | ------------------------------- | --------------------------------------------------------------- |
|
|
63
|
+
| **gpt-image-2** | 细节最丰富、光影最真实、网格/布局理解力最强、中文字符准确率最高、3 provider fallback 容错高 | 概念图、精灵图直出、含文字素材、写实/半写实风格 | 慢(7-8min/张),**不设 timeout** | 慢是正常的;只有 `API 500` / `fetch failed` 才切换下一 provider |
|
|
64
|
+
| **gemini-3.1-flash-image-preview** | 速度快、色彩鲜艳、构图清晰、最稳定不易报错、reference-image 理解好 | 快速迭代、扁平卡通、明亮休闲风、超时回退 | 快(<30s) | 精细细节和文字不如 gpt-image-2 |
|
|
65
|
+
| **hy-image-v3.0** | 亚洲审美偏向、角色设计好、Q版/IP风格擅长 | 中国风、Q版角色、IP 形象 | 中等 | 西方写实风格表现一般 |
|
|
66
|
+
| **flux-kontext-pro** | 艺术感强、风格独特、手绘质感好 | 艺术插画、手绘风、独特画风探索 | 中等 | 网格理解弱,不适合精灵图 |
|
|
67
|
+
| **nano-banana-2** | 最快、适合简单图形探索 | 简洁图标、平面设计、快速草稿 | 极快 | 中文字符不可靠、细节少 |
|
|
68
|
+
| **qwen-image-edit-max** | 图生图编辑能力强、局部修改精确 | 换花色/换数字/换颜色等结构相似变体 | 中等 | CAPABILITY=image→image,必须传 reference-image |
|
|
69
|
+
| **wan2.6-image** | 风格迁移能力好 | 将参考图风格应用到新内容 | 中等 | CAPABILITY=image→image,必须传 reference-image |
|
|
70
|
+
|
|
71
|
+
### 选型口诀
|
|
72
|
+
|
|
73
|
+
- 追求质量 / 含文字 / 网格图 → `gpt-image-2`
|
|
74
|
+
- 快速迭代 / 扁平风格 / 回退备选 → `gemini-3.1-flash-image-preview`
|
|
75
|
+
- 中国风 / Q版 / 亚洲审美 → `hy-image-v3.0`
|
|
76
|
+
- 艺术/手绘探索 → `flux-kontext-pro`
|
|
77
|
+
- 纯 edit 变体 → `qwen-image-edit-max`
|
|
78
|
+
- 一切都失败 → `gemini-3.1-flash-image-preview`(最稳定)
|
|
79
|
+
|
|
80
|
+
### 为什么精灵图直出是首选批量策略?
|
|
81
|
+
|
|
82
|
+
精灵图直出(一次 API 调用生成一张包含 N 个元素的网格图,再 sprite-split 切分)优于逐个生成,原因有三:
|
|
83
|
+
|
|
84
|
+
1. **成本**:一次 API 调用可包含 4-16 个元素,而逐个生成 N 个元素需要 N 次调用,成本和耗时线性增长。
|
|
85
|
+
2. **风格一致性**:同一张图中的所有元素共享相同的生成上下文,天然保证色调、线条、阴影风格统一,避免逐个生成时每张图风格漂移的问题。
|
|
86
|
+
3. **无逐元素偏差**:逐个生成时即使用相同 prompt,模型每次采样结果都不同,元素之间会出现肉眼可见的不一致;精灵图在一次推理中完成,消除了这种 per-element drift。
|
|
87
|
+
|
|
88
|
+
详细策略与代码示例见进阶参考。
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 2. 核心命令
|
|
93
|
+
|
|
94
|
+
### 文生图(text-to-image)
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
playcraft tools generate-image \
|
|
98
|
+
--prompt "A golden sword icon, flat 2D vector style, centered composition" \
|
|
99
|
+
--output assets/images/sword.png \
|
|
100
|
+
--aspect-ratio 1:1 \
|
|
101
|
+
--image-size 4K \
|
|
102
|
+
--image-model gpt-image-2
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 图生图(image-to-image / reference-image)
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
playcraft tools generate-image \
|
|
109
|
+
--prompt "A similar style silver shield icon, same art direction" \
|
|
110
|
+
--output assets/images/shield.png \
|
|
111
|
+
--reference-image assets/images/sword.png \
|
|
112
|
+
--aspect-ratio 1:1 \
|
|
113
|
+
--image-size 4K \
|
|
114
|
+
--image-model gpt-image-2
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 参数说明
|
|
118
|
+
|
|
119
|
+
| 参数 | 值 | 说明 |
|
|
120
|
+
| ---------------------- | ----------------------------------------- | ------------------------------------------------------- |
|
|
121
|
+
| `--aspect-ratio` | `1:1` / **`45:16`** / `16:9` / `9:16` / … | **Master Composite(5×9:16 横排)用 `45:16`** |
|
|
122
|
+
| `--width` / `--height` | 像素整数(成对) | MC 推荐 **`3600×1280`** 或 **`4096×1455`** |
|
|
123
|
+
| `--image-size` | `1K` / `2K` / `4K` | 控制分辨率级别。实际像素取决于模型+比例的组合(见下表) |
|
|
124
|
+
|
|
125
|
+
**`--image-size` 对 gpt-image-2 的落盘尺寸**(MuleRouter 请求 `size=auto`,由后端 letterbox 到目标像素):
|
|
126
|
+
|
|
127
|
+
| aspectRatio | 1K | 2K | 4K |
|
|
128
|
+
| ----------------------------- | --------- | ------------- | ------------- |
|
|
129
|
+
| 1:1 | 1024x1024 | 2048x2048 | 2048x2048 |
|
|
130
|
+
| **45:16** (5×9:16 故事板横排) | 2048x728 | **3600x1280** | **4096x1455** |
|
|
131
|
+
| 16:9 | 2048x1152 | 2048x1152 | 3840x2160 |
|
|
132
|
+
| 9:16 | 2160x3840 | 2160x3840 | 2160x3840 |
|
|
133
|
+
| 3:2 / 4:3 | 1536x1024 | 1536x1024 | 1536x1024 |
|
|
134
|
+
| 21:9 | 2048x1152 | 3840x2160 | 3840x2160 |
|
|
135
|
+
|
|
136
|
+
> **IEGG LiteLLM** 在 fallback 链中排第一,但 Azure gpt-image 最大约 1536px 宽 — 达不到 5×9:16 横排。故事板请用 **`mulerouter/gpt-image-2`**(跳过 litellm)+ `--width 3600 --height 1280`(或 4096×1455)。
|
|
137
|
+
|
|
138
|
+
### Master Composite(MC)模型白名单
|
|
139
|
+
|
|
140
|
+
Designer 生成 MC 时**不得**套用上方通用决策树里的 gemini / flux / hy-image 等——MC 只允许:
|
|
141
|
+
|
|
142
|
+
| 允许 | 禁止(MC) |
|
|
143
|
+
| -------------------------- | --------------------------------------------------- |
|
|
144
|
+
| `mulerouter/gpt-image-2` | `gemini-*`、`flux-*`、`hy-image-*`、`wan*`、`qwen*` |
|
|
145
|
+
| `mulerouter/nano-banana-2` | 裸 `gpt-image-2`(会走 iegg-litellm 压扁五格) |
|
|
146
|
+
|
|
147
|
+
完整 prompt / 尺寸规范见 **`playcraft-storyboard`** skill。ASR 双板 `--image-model` 须与已选 MC **完全一致**。
|
|
148
|
+
|
|
149
|
+
**Phase 1(概念图)**:纯文生图,`--image-size 4K` + MC 白名单内模型。
|
|
150
|
+
**Phase 2(批量生产)**:必须带 `--reference-image` 指向 Phase 1 确认的概念图,同一批次使用**相同模型 + 相同 reference image + 相同 image-size**。
|
|
151
|
+
|
|
152
|
+
### --reference-image 路径支持
|
|
153
|
+
|
|
154
|
+
- 本地绝对路径:`/path/to/ref.png`
|
|
155
|
+
- 相对路径(相对 CWD):`assets/images/ref.png`
|
|
156
|
+
- HTTP(S) URL(CLI 自动下载):`"https://images.unsplash.com/photo-xxx"`
|
|
157
|
+
- 多张参考图(重复选项,最多 8 张)
|
|
158
|
+
- 混合本地文件 + URL
|
|
159
|
+
- `search-image` 返回的 `downloadUrl` 可直接使用
|
|
160
|
+
|
|
161
|
+
### 常用辅助命令
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# 去背景(绿幕建议 tolerance=25;默认白背景可用 15)
|
|
165
|
+
playcraft image remove-background --input in.png --output out.png --tolerance 25
|
|
166
|
+
|
|
167
|
+
# 缩放
|
|
168
|
+
playcraft image resize --input in.png --output out.png --width 128 --height 128
|
|
169
|
+
|
|
170
|
+
# 精灵图切分
|
|
171
|
+
playcraft image sprite-split \
|
|
172
|
+
--input sheet.png --output-dir frames/ \
|
|
173
|
+
--rows 2 --columns 4 \
|
|
174
|
+
--frame-width <总宽/4> --frame-height <总高/2>
|
|
175
|
+
|
|
176
|
+
# 合并精灵图
|
|
177
|
+
playcraft image sprite-sheet \
|
|
178
|
+
--input-dir processed/ --output bundle \
|
|
179
|
+
--columns 8 --background white
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## 3. 背景处理决策树
|
|
185
|
+
|
|
186
|
+
> **核心原则**:先确定素材类型,再决定是否需要去背景,再按素材主色调选择色幕颜色,最后做去背景 + 深色背景验证。
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
生成的素材需要做什么?
|
|
190
|
+
│
|
|
191
|
+
├─ 需要透明背景的独立元素(角色、道具、图标、文字图片、HUD、VFX)?
|
|
192
|
+
│ ├─ 检查模型 ALPHA 列(playcraft tools list-image-models)
|
|
193
|
+
│ ├─ ALPHA = yes → prompt 加 "transparent background, PNG with alpha"
|
|
194
|
+
│ └─ ALPHA = no → 色幕策略(按素材主色调选择):
|
|
195
|
+
│ ├─ 素材为金色/黄色/橙色/绿色 → 蓝幕 #0000FF
|
|
196
|
+
│ │ ├─ prompt 末尾加 "on solid bright blue #0000FF background, isolated, centered"
|
|
197
|
+
│ │ └─ 后处理:remove-background(tolerance 25)→ 深色背景验证
|
|
198
|
+
│ ├─ 素材为蓝色/紫色/靛蓝 → 绿幕 #00FF00
|
|
199
|
+
│ │ ├─ prompt 末尾加 "on solid bright green #00FF00 background, isolated, centered"
|
|
200
|
+
│ │ └─ 后处理:remove-background(tolerance 25)→ 深色背景验证
|
|
201
|
+
│ └─ 其他颜色 → 绿幕 #00FF00(默认)
|
|
202
|
+
│ ├─ prompt 末尾加 "on solid bright green #00FF00 background, isolated, centered"
|
|
203
|
+
│ └─ 后处理:remove-background(tolerance 25)→ 深色背景验证
|
|
204
|
+
│
|
|
205
|
+
├─ 卡牌/棋盘格子/UI 面板(本身就是矩形有边框)?
|
|
206
|
+
│ ├─ 不要去背景!白色牌面就是牌的一部分
|
|
207
|
+
│ ├─ prompt 加 "flat 2D, no drop shadow, no 3D effect, clean edges"
|
|
208
|
+
│ └─ 后处理:直接 resize(skipRemoveBg = true)
|
|
209
|
+
│
|
|
210
|
+
└─ 场景背景(天空、地面、全屏图)?
|
|
211
|
+
├─ 不做背景处理,直接 resize
|
|
212
|
+
└─ ⚠️ prompt 必须加 "full bleed, edge to edge, no vignette, no fade"
|
|
213
|
+
→ 后处理验证:四角四边颜色一致,无褪色/白色
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### 为什么需要色幕策略?
|
|
217
|
+
|
|
218
|
+
**当前所有已接入模型均不支持原生透明 PNG(ALPHA = no)**。白色背景 + 白色物体 = floodfill 无法区分边界。纯色背景 + 非撞色游戏素材 = 抠图几乎完美。与影视 chroma-key 原理相同。
|
|
219
|
+
|
|
220
|
+
### 色幕选型矩阵(Color Conflict Avoidance)
|
|
221
|
+
|
|
222
|
+
> ⚠️ **不要默认全用绿幕!** 金色素材 + 绿幕 = 绿色与金色色相接近,floodfill 会在边缘残留绿色,或误删金色像素。
|
|
223
|
+
|
|
224
|
+
| 素材主色调 | ❌ 禁用 | ✅ 推荐 | 原因 |
|
|
225
|
+
| --------------------- | ------- | ---------------- | ------------------------------------------ |
|
|
226
|
+
| 金色/黄色/橙色/琥珀色 | 绿幕 | **蓝幕 #0000FF** | 金色与绿色色相接近(黄绿区),边缘残留绿色 |
|
|
227
|
+
| 绿色/翠绿/青色 | 绿幕 | **蓝幕 #0000FF** | 同色系,无法区分边界 |
|
|
228
|
+
| 蓝色/靛蓝/紫色 | 蓝幕 | **绿幕 #00FF00** | 同色系冲突 |
|
|
229
|
+
| 红色/粉色/白色/黑色 | — | 绿幕或蓝幕均可 | 与两者色相距离都大 |
|
|
230
|
+
| 多色/彩虹 | — | **品红 #FF00FF** | 品红在自然素材中最少出现(最后手段) |
|
|
231
|
+
|
|
232
|
+
### Prompt 后缀速查表
|
|
233
|
+
|
|
234
|
+
| 素材类型 | prompt 建议后缀 | 后处理 | skipRemoveBg |
|
|
235
|
+
| -------------------------- | ------------------------------------------------------------------- | ------------------------------------------- | ------------ |
|
|
236
|
+
| 独立元素(需透明,非金色) | `"on solid bright green #00FF00 background, isolated, centered"` | remove-background (tolerance 25) → 深色验证 | `false` |
|
|
237
|
+
| 独立元素(金/黄/橙/绿色) | `"on solid bright blue #0000FF background, isolated, centered"` | remove-background (tolerance 25) → 深色验证 | `false` |
|
|
238
|
+
| 独立元素(多色/彩虹) | `"on solid bright magenta #FF00FF background, isolated, centered"` | remove-background (tolerance 20) → 深色验证 | `false` |
|
|
239
|
+
| VFX 特效(发光/粒子) | `"on solid bright blue #0000FF background, isolated, centered"` | remove-background (tolerance 25) → 逐帧验证 | `false` |
|
|
240
|
+
| 卡牌/棋子(矩形本体) | `"flat 2D, no shadow, no 3D effect, clean edges, white background"` | 只做 resize | `true` |
|
|
241
|
+
| UI 按钮/面板 | `"flat UI element, no shadow, clean edges"` | trim + resize | `true` |
|
|
242
|
+
| 场景背景 | `"full scene, full bleed, edge to edge, no vignette, no fade"` | resize → 四角全覆盖检查 | `true` |
|
|
243
|
+
|
|
244
|
+
### 去背景后必做:深色背景渲证
|
|
245
|
+
|
|
246
|
+
> 每个需要透明的资产,去背景后**必须**在深色背景上叠合验证。这一步是防止白底/色幕残留/黑块的最后防线。
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
playcraft image overlay \
|
|
250
|
+
--base ta-workspace/tmp/dark_verify_bg.png \
|
|
251
|
+
--overlay <processed>.png \
|
|
252
|
+
--output ta-workspace/tmp/verify_<name>.png \
|
|
253
|
+
--gravity center
|
|
254
|
+
# 目视检查:无白边、无色幕残留色、无黑色不透明块、边缘干净
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**不通过时的修复**:见 `ta-pipeline-cookbook.md` Step 0e §3 及 Compliance Gate 失败修复表。
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## 4. 脚本决策
|
|
262
|
+
|
|
263
|
+
- **<=5 个文件** → 直接使用 CLI 命令
|
|
264
|
+
- **>5 个文件** → 编写 Node.js 脚本放入 `ta-workspace/scripts/`。脚本模板见 `reference/` 目录。
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## 5. 进阶参考
|
|
269
|
+
|
|
270
|
+
需要以下进阶内容时,查阅 [`refs/ta-image-generation-detail.md`](../../agents/refs/ta-image-generation-detail.md):
|
|
271
|
+
|
|
272
|
+
| 主题 | 说明 |
|
|
273
|
+
| --------------------- | ------------------------------------------------------------------- |
|
|
274
|
+
| 精灵图直出策略 | gpt-image-2 网格图生成 + sprite-split 切分,白底/绿幕示例,分批策略 |
|
|
275
|
+
| Edit 模型工作流 | 基图 → edit 变体(换花色/换数字/换颜色),prompt 写法 |
|
|
276
|
+
| 错误恢复表 | 各类错误现象、原因、立即行动;模型回退顺序 |
|
|
277
|
+
| Provider 回退链 | gpt-image-2 三 provider 互为回退 + 非 gpt-image-2 稳定回退 |
|
|
278
|
+
| 批量生成最佳实践 | 单张验证、Node.js 脚本推荐、脚本模板使用 |
|
|
279
|
+
| ta-workspace 脚本驱动 | 为什么用 Node.js、目录约定、脚本模板一览表 |
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// build-sprite-sheet.template.mjs — 精灵图合并 + 验证脚本模板
|
|
3
|
+
// 用途:将处理好的图片目录合并为精灵图(PNG + JSON 元数据)
|
|
4
|
+
//
|
|
5
|
+
// 使用方式:
|
|
6
|
+
// 1. cp .claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs ta-workspace/scripts/build-<name>-atlas.mjs
|
|
7
|
+
// 2. 修改下方 CONFIG 区域
|
|
8
|
+
// 3. 如需控制帧顺序,填写 ORDERED_NAMES 数组(留空则按文件名排序)
|
|
9
|
+
// 4. node ta-workspace/scripts/build-<name>-atlas.mjs
|
|
10
|
+
// ============================================================
|
|
11
|
+
|
|
12
|
+
import { execSync } from 'child_process';
|
|
13
|
+
import { existsSync, readFileSync, readdirSync, mkdirSync, appendFileSync } from 'fs';
|
|
14
|
+
import { join, extname } from 'path';
|
|
15
|
+
|
|
16
|
+
// ============ CONFIG — 修改这里 ============
|
|
17
|
+
const CONFIG = {
|
|
18
|
+
/** 处理好的图片目录(process-batch 的输出目录) */
|
|
19
|
+
processedDir: 'assets/images/processed',
|
|
20
|
+
/** 输出文件路径(不含扩展名,会生成 .png 和 .json 两个文件) */
|
|
21
|
+
outputBase: 'assets/bundles/atlas',
|
|
22
|
+
/** 每行帧数 */
|
|
23
|
+
columns: 8,
|
|
24
|
+
/** 帧间距(像素) */
|
|
25
|
+
padding: 2,
|
|
26
|
+
/**
|
|
27
|
+
* 精灵图背景色
|
|
28
|
+
* - 'transparent'(默认):透明背景
|
|
29
|
+
* - 'white' / 'black' / '#RRGGBB':固定背景色
|
|
30
|
+
* 某些引擎(如 PlayCanvas)处理透明精灵图时可能显示为黑色,此时用 'white' 更安全
|
|
31
|
+
*/
|
|
32
|
+
background: 'transparent',
|
|
33
|
+
/** 文件名排序方式:'name'(字母序)或 'custom'(使用 ORDERED_NAMES) */
|
|
34
|
+
sort: 'name',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 自定义帧顺序(CONFIG.sort = 'custom' 时生效)
|
|
39
|
+
* 填写不含扩展名的文件名,按期望顺序排列
|
|
40
|
+
* 示例(扑克牌):['spades_A', 'spades_2', 'spades_3', ...]
|
|
41
|
+
*/
|
|
42
|
+
const ORDERED_NAMES = [
|
|
43
|
+
// 填写文件名(不含扩展名)...
|
|
44
|
+
];
|
|
45
|
+
// ============ LOGIC — 通常不需要修改 ============
|
|
46
|
+
|
|
47
|
+
const LOG_FILE = 'ta-workspace/logs/build-sprite-sheet.log';
|
|
48
|
+
mkdirSync('ta-workspace/logs', { recursive: true });
|
|
49
|
+
|
|
50
|
+
function log(msg) {
|
|
51
|
+
const line = `[${new Date().toISOString()}] ${msg}`;
|
|
52
|
+
console.log(line);
|
|
53
|
+
appendFileSync(LOG_FILE, line + '\n');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 收集输入文件
|
|
57
|
+
let inputFiles;
|
|
58
|
+
if (CONFIG.sort === 'custom' && ORDERED_NAMES.length > 0) {
|
|
59
|
+
// 自定义顺序:按 ORDERED_NAMES 排列,找不到的文件跳过并警告
|
|
60
|
+
inputFiles = [];
|
|
61
|
+
for (const name of ORDERED_NAMES) {
|
|
62
|
+
const candidates = ['.png', '.jpg', '.webp'].map(ext => join(CONFIG.processedDir, `${name}${ext}`));
|
|
63
|
+
const found = candidates.find(p => existsSync(p));
|
|
64
|
+
if (found) {
|
|
65
|
+
inputFiles.push(found);
|
|
66
|
+
} else {
|
|
67
|
+
log(`[WARN] Ordered file not found: ${name} (skipping)`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
// 文件名排序
|
|
72
|
+
inputFiles = readdirSync(CONFIG.processedDir)
|
|
73
|
+
.filter(f => ['.png', '.jpg', '.webp'].includes(extname(f).toLowerCase()))
|
|
74
|
+
.sort()
|
|
75
|
+
.map(f => join(CONFIG.processedDir, f));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (inputFiles.length === 0) {
|
|
79
|
+
log('ERROR: No input files found in ' + CONFIG.processedDir);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
log(`=== Build Sprite Sheet Start ===`);
|
|
84
|
+
log(`Frames: ${inputFiles.length} | Columns: ${CONFIG.columns} | Padding: ${CONFIG.padding} | BG: ${CONFIG.background}`);
|
|
85
|
+
|
|
86
|
+
// 构建 --inputs 参数列表
|
|
87
|
+
const inputsArg = inputFiles.map(p => `"${p}"`).join(' ');
|
|
88
|
+
|
|
89
|
+
const cmd =
|
|
90
|
+
`playcraft image sprite-sheet` +
|
|
91
|
+
` --inputs ${inputsArg}` +
|
|
92
|
+
` --output "${CONFIG.outputBase}"` +
|
|
93
|
+
` --columns ${CONFIG.columns}` +
|
|
94
|
+
` --padding ${CONFIG.padding}` +
|
|
95
|
+
(CONFIG.background !== 'transparent' ? ` --background "${CONFIG.background}"` : '');
|
|
96
|
+
|
|
97
|
+
log(`Running: playcraft image sprite-sheet ... --output ${CONFIG.outputBase}`);
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
101
|
+
} catch (e) {
|
|
102
|
+
log(`ERROR: sprite-sheet command failed: ${e.message}`);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 验证输出
|
|
107
|
+
const pngPath = `${CONFIG.outputBase}.png`;
|
|
108
|
+
const jsonPath = `${CONFIG.outputBase}.json`;
|
|
109
|
+
|
|
110
|
+
if (!existsSync(pngPath) || !existsSync(jsonPath)) {
|
|
111
|
+
log('ERROR: Output files missing! Expected ' + pngPath + ' and ' + jsonPath);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const meta = JSON.parse(readFileSync(jsonPath, 'utf-8'));
|
|
116
|
+
const frameCount = Object.keys(meta.frames ?? {}).length;
|
|
117
|
+
|
|
118
|
+
log(`\n=== Sprite Sheet Built ===`);
|
|
119
|
+
log(` Frames : ${frameCount}`);
|
|
120
|
+
log(` PNG : ${pngPath}`);
|
|
121
|
+
log(` JSON : ${jsonPath}`);
|
|
122
|
+
log(` Columns : ${CONFIG.columns}`);
|
|
123
|
+
log(` Rows : ${Math.ceil(frameCount / CONFIG.columns)}`);
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* compare-style.template.mjs
|
|
3
|
+
*
|
|
4
|
+
* 色彩风格一致性定量检查脚本。
|
|
5
|
+
* 用途:TA Micro-Batch Validation 辅助工具,对 AI 补全素材与 Designer 样本做客观色彩比较。
|
|
6
|
+
*
|
|
7
|
+
* 使用方法:
|
|
8
|
+
* cp .claude/skills/playcraft-image-generation/reference/compare-style.template.mjs \
|
|
9
|
+
* ta-workspace/scripts/compare-style.mjs
|
|
10
|
+
* # 修改下方 CONFIG 区域后运行:
|
|
11
|
+
* node ta-workspace/scripts/compare-style.mjs
|
|
12
|
+
*
|
|
13
|
+
* 输出示例:
|
|
14
|
+
* [COMPARE] tile_sample_1.png vs tile_comp_01.png
|
|
15
|
+
* 色相距离(加权): 8.3° → ✅ 优秀(< 15°)
|
|
16
|
+
* 明度差异: 0.06 → ✅ 优秀(< 0.10)
|
|
17
|
+
* 饱和度差异: 0.04 → ✅ 优秀(< 0.10)
|
|
18
|
+
* 综合一致性评分: 4.7 / 5 → ✅ 通过(≥ 3.0)
|
|
19
|
+
*
|
|
20
|
+
* 依赖:@gltf-transform/core(CLI 已内置)、sharp(CLI 已内置)
|
|
21
|
+
* 若 sharp 未全局可用,运行前:npm install sharp --no-save
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { execSync } from 'child_process';
|
|
25
|
+
import { existsSync, mkdirSync, appendFileSync } from 'fs';
|
|
26
|
+
import path from 'path';
|
|
27
|
+
|
|
28
|
+
// ─── CONFIG ───────────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
const CONFIG = {
|
|
31
|
+
/** Designer 样本图(风格基准) */
|
|
32
|
+
referenceImage: 'assets/images/tiles/tile_sample_1.png',
|
|
33
|
+
|
|
34
|
+
/** 要检查的 TA 补全素材目录或文件列表 */
|
|
35
|
+
targetDir: 'assets/images/tiles',
|
|
36
|
+
|
|
37
|
+
/** 只检查该目录下的哪些文件(glob 后缀匹配) */
|
|
38
|
+
targetGlob: '.png',
|
|
39
|
+
|
|
40
|
+
/** 排除文件名包含这些关键词的文件(Designer 样本自身) */
|
|
41
|
+
excludeKeywords: ['sample', 'concept', 'selected', 'storyboard'],
|
|
42
|
+
|
|
43
|
+
/** 日志输出路径 */
|
|
44
|
+
logFile: 'ta-workspace/logs/style-compare.log',
|
|
45
|
+
|
|
46
|
+
/** 评分阈值(低于此值输出警告) */
|
|
47
|
+
warnThreshold: 3.0,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// ─── HELPERS ──────────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 用 playcraft image info 提取图片的 dominant colors(HSL 列表)
|
|
54
|
+
* 返回格式:[{ h, s, l, weight }](weight 为面积占比估算)
|
|
55
|
+
*/
|
|
56
|
+
function extractDominantColors(imagePath) {
|
|
57
|
+
try {
|
|
58
|
+
const raw = execSync(
|
|
59
|
+
`playcraft image info --input "${imagePath}" --json`,
|
|
60
|
+
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
61
|
+
);
|
|
62
|
+
const info = JSON.parse(raw);
|
|
63
|
+
|
|
64
|
+
// playcraft image info 返回 dominantColors 数组:[{ r, g, b, hex, percent }]
|
|
65
|
+
const colors = (info.dominantColors || []).slice(0, 5);
|
|
66
|
+
return colors.map(c => {
|
|
67
|
+
const hsl = rgbToHsl(c.r, c.g, c.b);
|
|
68
|
+
return { ...hsl, weight: (c.percent || 20) / 100 };
|
|
69
|
+
});
|
|
70
|
+
} catch {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function rgbToHsl(r, g, b) {
|
|
76
|
+
r /= 255; g /= 255; b /= 255;
|
|
77
|
+
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
78
|
+
const l = (max + min) / 2;
|
|
79
|
+
if (max === min) return { h: 0, s: 0, l };
|
|
80
|
+
const d = max - min;
|
|
81
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
82
|
+
let h;
|
|
83
|
+
switch (max) {
|
|
84
|
+
case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
|
|
85
|
+
case g: h = ((b - r) / d + 2) / 6; break;
|
|
86
|
+
default: h = ((r - g) / d + 4) / 6;
|
|
87
|
+
}
|
|
88
|
+
return { h: h * 360, s, l };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 计算两组 dominant colors 之间的加权色相距离(°)
|
|
93
|
+
* 色相距离取最短弧(0–180°)
|
|
94
|
+
*/
|
|
95
|
+
function weightedHueDist(colorsA, colorsB) {
|
|
96
|
+
if (!colorsA.length || !colorsB.length) return 999;
|
|
97
|
+
let totalDist = 0, totalWeight = 0;
|
|
98
|
+
for (const a of colorsA) {
|
|
99
|
+
// 找 B 中色相最近的颜色
|
|
100
|
+
let minDist = Infinity;
|
|
101
|
+
for (const b of colorsB) {
|
|
102
|
+
const raw = Math.abs(a.h - b.h);
|
|
103
|
+
const dist = raw > 180 ? 360 - raw : raw;
|
|
104
|
+
if (dist < minDist) minDist = dist;
|
|
105
|
+
}
|
|
106
|
+
totalDist += minDist * a.weight;
|
|
107
|
+
totalWeight += a.weight;
|
|
108
|
+
}
|
|
109
|
+
return totalWeight > 0 ? totalDist / totalWeight : 999;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 计算明度 / 饱和度的加权平均差异(0–1)
|
|
114
|
+
*/
|
|
115
|
+
function weightedChannelDiff(colorsA, colorsB, channel) {
|
|
116
|
+
if (!colorsA.length || !colorsB.length) return 1;
|
|
117
|
+
const avgA = colorsA.reduce((s, c) => s + c[channel] * c.weight, 0) /
|
|
118
|
+
colorsA.reduce((s, c) => s + c.weight, 0);
|
|
119
|
+
const avgB = colorsB.reduce((s, c) => s + c[channel] * c.weight, 0) /
|
|
120
|
+
colorsB.reduce((s, c) => s + c.weight, 0);
|
|
121
|
+
return Math.abs(avgA - avgB);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 综合一致性评分 1–5
|
|
126
|
+
*
|
|
127
|
+
* 评分规则(与 playcraft-style-qa Micro-Batch 标准对齐):
|
|
128
|
+
* 色相距离 < 15° 且 明度/饱和度差 < 0.10 → 5 分
|
|
129
|
+
* 色相距离 < 20° 且 明度/饱和度差 < 0.15 → 4 分
|
|
130
|
+
* 色相距离 < 30° 且 明度/饱和度差 < 0.20 → 3 分(勉强可接受)
|
|
131
|
+
* 色相距离 < 45° 且 明度/饱和度差 < 0.30 → 2 分(需调整)
|
|
132
|
+
* 其他 → 1 分(停止,提问 Designer)
|
|
133
|
+
*/
|
|
134
|
+
function calcScore(hueDist, lightDiff, satDiff) {
|
|
135
|
+
const channelOk = Math.max(lightDiff, satDiff);
|
|
136
|
+
if (hueDist < 15 && channelOk < 0.10) return 5;
|
|
137
|
+
if (hueDist < 20 && channelOk < 0.15) return 4;
|
|
138
|
+
if (hueDist < 30 && channelOk < 0.20) return 3;
|
|
139
|
+
if (hueDist < 45 && channelOk < 0.30) return 2;
|
|
140
|
+
return 1;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function scoreLabel(score) {
|
|
144
|
+
if (score >= 4) return '✅ 良好';
|
|
145
|
+
if (score >= 3) return '⚠️ 可接受';
|
|
146
|
+
return '❌ 偏差过大,需停止';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function hueLabel(dist) {
|
|
150
|
+
if (dist < 15) return '✅ 优秀(< 15°)';
|
|
151
|
+
if (dist < 30) return '⚠️ 偏差(< 30°)';
|
|
152
|
+
return '❌ 严重偏差(≥ 30°)';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function channelLabel(diff) {
|
|
156
|
+
if (diff < 0.10) return '✅ 优秀(< 0.10)';
|
|
157
|
+
if (diff < 0.20) return '⚠️ 偏差(< 0.20)';
|
|
158
|
+
return '❌ 严重偏差(≥ 0.20)';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ─── MAIN ─────────────────────────────────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
async function main() {
|
|
164
|
+
const logDir = path.dirname(CONFIG.logFile);
|
|
165
|
+
if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });
|
|
166
|
+
|
|
167
|
+
const refPath = CONFIG.referenceImage;
|
|
168
|
+
if (!existsSync(refPath)) {
|
|
169
|
+
console.error(`[ERROR] 参考图不存在: ${refPath}`);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log(`\n📊 色彩风格一致性检查`);
|
|
174
|
+
console.log(` 参考图: ${refPath}\n`);
|
|
175
|
+
|
|
176
|
+
const refColors = extractDominantColors(refPath);
|
|
177
|
+
if (!refColors.length) {
|
|
178
|
+
console.error('[ERROR] 无法从参考图提取颜色,请确认 playcraft image info 可用');
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 收集目标文件
|
|
183
|
+
let targets = [];
|
|
184
|
+
try {
|
|
185
|
+
const { readdirSync } = await import('fs');
|
|
186
|
+
targets = readdirSync(CONFIG.targetDir)
|
|
187
|
+
.filter(f => f.endsWith(CONFIG.targetGlob))
|
|
188
|
+
.filter(f => !CONFIG.excludeKeywords.some(kw => f.includes(kw)))
|
|
189
|
+
.map(f => path.join(CONFIG.targetDir, f));
|
|
190
|
+
} catch {
|
|
191
|
+
console.error(`[ERROR] 无法读取目录: ${CONFIG.targetDir}`);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!targets.length) {
|
|
196
|
+
console.log(' 未找到需要检查的目标文件');
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const results = [];
|
|
201
|
+
let failCount = 0;
|
|
202
|
+
|
|
203
|
+
for (const target of targets) {
|
|
204
|
+
const targetColors = extractDominantColors(target);
|
|
205
|
+
if (!targetColors.length) {
|
|
206
|
+
console.log(` [SKIP] ${path.basename(target)} — 无法提取颜色`);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const hueDist = weightedHueDist(refColors, targetColors);
|
|
211
|
+
const lightDiff = weightedChannelDiff(refColors, targetColors, 'l');
|
|
212
|
+
const satDiff = weightedChannelDiff(refColors, targetColors, 's');
|
|
213
|
+
const score = calcScore(hueDist, lightDiff, satDiff);
|
|
214
|
+
|
|
215
|
+
if (score < CONFIG.warnThreshold) failCount++;
|
|
216
|
+
|
|
217
|
+
const line = [
|
|
218
|
+
`[COMPARE] ${path.basename(refPath)} vs ${path.basename(target)}`,
|
|
219
|
+
` 色相距离(加权): ${hueDist.toFixed(1)}° → ${hueLabel(hueDist)}`,
|
|
220
|
+
` 明度差异: ${lightDiff.toFixed(2)} → ${channelLabel(lightDiff)}`,
|
|
221
|
+
` 饱和度差异: ${satDiff.toFixed(2)} → ${channelLabel(satDiff)}`,
|
|
222
|
+
` 综合一致性评分: ${score} / 5 → ${scoreLabel(score)}`,
|
|
223
|
+
'',
|
|
224
|
+
].join('\n');
|
|
225
|
+
|
|
226
|
+
console.log(line);
|
|
227
|
+
results.push({ file: path.basename(target), score, hueDist, lightDiff, satDiff });
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// 汇总
|
|
231
|
+
const avgScore = results.reduce((s, r) => s + r.score, 0) / (results.length || 1);
|
|
232
|
+
const summary = [
|
|
233
|
+
`=== 汇总 ===`,
|
|
234
|
+
` 检查文件数: ${results.length}`,
|
|
235
|
+
` 平均评分: ${avgScore.toFixed(1)} / 5`,
|
|
236
|
+
` 低于阈值数: ${failCount}(阈值 ${CONFIG.warnThreshold})`,
|
|
237
|
+
failCount > 0
|
|
238
|
+
? ` ⚠️ 建议:停止低分文件类型的批量生产,调整生成参数或向 Designer 提问 ICP`
|
|
239
|
+
: ` ✅ 全部通过,可继续批量生产`,
|
|
240
|
+
'',
|
|
241
|
+
].join('\n');
|
|
242
|
+
|
|
243
|
+
console.log(summary);
|
|
244
|
+
|
|
245
|
+
// 写日志
|
|
246
|
+
const timestamp = new Date().toISOString();
|
|
247
|
+
appendFileSync(CONFIG.logFile, `\n[${timestamp}]\n${results.map(r =>
|
|
248
|
+
`${r.file}: score=${r.score} hue=${r.hueDist.toFixed(1)}° L=${r.lightDiff.toFixed(2)} S=${r.satDiff.toFixed(2)}`
|
|
249
|
+
).join('\n')}\n${summary}`);
|
|
250
|
+
|
|
251
|
+
if (failCount > 0) process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
main().catch(e => { console.error(e); process.exit(1); });
|