215 lines
6.7 KiB
Markdown
215 lines
6.7 KiB
Markdown
|
|
# 06 — 精灵图处理管线
|
|||
|
|
|
|||
|
|
> **一句话概括**:自动精灵图处理管线 — 背景移除 → 投影检测 → 切割 → 对齐 → GIF 预览,将一张 AI 生成的 Sprite Sheet 无缝转化为可用的逐帧动画资源。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart LR
|
|||
|
|
A["🖼️ Input PNG<br/>Sprite Sheet"] --> B["🪄 Background<br/>Removal"]
|
|||
|
|
B --> C["📊 Gap<br/>Detection"]
|
|||
|
|
C --> D["✂️ Tile<br/>Extract"]
|
|||
|
|
D --> E["🔍 Filter<br/>MinFill"]
|
|||
|
|
E --> F["📐 Trim<br/>Alpha"]
|
|||
|
|
F --> G["🎯 Align<br/>padToLargest"]
|
|||
|
|
G --> H["🎬 GIF<br/>Preview"]
|
|||
|
|
|
|||
|
|
style A fill:#e3f2fd,stroke:#1976d2
|
|||
|
|
style B fill:#fff3e0,stroke:#f57c00
|
|||
|
|
style C fill:#e8f5e9,stroke:#388e3c
|
|||
|
|
style D fill:#fce4ec,stroke:#c62828
|
|||
|
|
style E fill:#f3e5f5,stroke:#7b1fa2
|
|||
|
|
style F fill:#e0f7fa,stroke:#00838f
|
|||
|
|
style G fill:#fff8e1,stroke:#f9a825
|
|||
|
|
style H fill:#e8eaf6,stroke:#303f9f
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📋 处理管线总览
|
|||
|
|
|
|||
|
|
AI 生成的 Sprite Sheet 通常包含白色/绿色背景、不均匀间距和尺寸差异。Gen2D 的精灵图处理管线自动完成从"原始 PNG"到"可用动画帧"的全部转换工作,无需用户手动操作。
|
|||
|
|
|
|||
|
|
**管线核心入口**:`splitsprite.Process(img, opts)`
|
|||
|
|
|
|||
|
|
| 步骤 | 函数 | 作用 |
|
|||
|
|
|:---:|------|------|
|
|||
|
|
| 1 | `removeWhiteBg` / `removeGreenScreen` | 移除背景色,生成透明通道 |
|
|||
|
|
| 2 | `projectionSplit` / `fixedGridSplit` | 检测帧边界,确定切割位置 |
|
|||
|
|
| 3 | `MinFillRatio` 过滤 | 丢弃空白/低填充率的无效帧 |
|
|||
|
|
| 4 | `trimAlpha` | 去除每帧的透明边框,减少冗余像素 |
|
|||
|
|
| 5 | `padToLargest` | 统一画布尺寸,底部居中对齐 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🪄 步骤 1:背景移除
|
|||
|
|
|
|||
|
|
AI 生成的图片通常带有纯色背景,管线支持两种模式:
|
|||
|
|
|
|||
|
|
### 白底模式(WhiteBg)
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// 阈值距离 #FFFFFF + alpha 渐变
|
|||
|
|
dist := max(255-R, max(255-G, 255-B))
|
|||
|
|
if dist < threshold/2 → 全透明
|
|||
|
|
if dist < threshold → alpha 线性渐变(抗锯齿)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- **WhiteThreshold** 默认 40,距离纯白 40 以内的像素被处理
|
|||
|
|
- 采用渐变 alpha 实现平滑过渡,避免硬边缘
|
|||
|
|
|
|||
|
|
### 绿幕模式(GreenScreen)
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// 绿色主导检测
|
|||
|
|
gDominance := G - (R+B)/2
|
|||
|
|
if gDominance > tolerance*255 → 透明化
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- **GreenTolerance** 默认 0.2,控制绿色检测灵敏度
|
|||
|
|
- 适用于绿色背景的 AI 生成图
|
|||
|
|
|
|||
|
|
> 💡 **设计选择**:两种模式互斥,`Process()` 根据 `opts.WhiteBg` / `opts.GreenScreen` 自动选择。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📊 步骤 2:切割策略
|
|||
|
|
|
|||
|
|
管线提供两种切割方式,根据配置自动切换:
|
|||
|
|
|
|||
|
|
### 投影检测(Projection Detection)
|
|||
|
|
|
|||
|
|
这是 Gen2D 的核心创新点。传统全局投影在检测列边界时,会因为武器、尾巴、翅膀等突出物被"稀释"而导致切割不准。
|
|||
|
|
|
|||
|
|
**两阶段投影算法**:
|
|||
|
|
|
|||
|
|
1. **全局行投影**:统计每行非透明像素占比,检测水平间隙
|
|||
|
|
- 行间隙通常是干净且全宽的,全局投影效果好
|
|||
|
|
|
|||
|
|
2. **逐行列投影**:在每个行段内独立计算列密度
|
|||
|
|
- 防止突出物(武器/尾巴)被其他行稀释
|
|||
|
|
- 每行段获得独立的列边界,互不干扰
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart TB
|
|||
|
|
A["输入图像"] --> B["全局行投影<br/>检测行间隙"]
|
|||
|
|
B --> C["行段 1"]
|
|||
|
|
B --> D["行段 2"]
|
|||
|
|
B --> E["行段 N"]
|
|||
|
|
C --> F["逐行列投影"]
|
|||
|
|
D --> G["逐行列投影"]
|
|||
|
|
E --> H["逐行列投影"]
|
|||
|
|
F --> I["tiles[]"]
|
|||
|
|
G --> I
|
|||
|
|
H --> I
|
|||
|
|
|
|||
|
|
style B fill:#e8f5e9,stroke:#388e3c
|
|||
|
|
style F fill:#fff3e0,stroke:#f57c00
|
|||
|
|
style G fill:#fff3e0,stroke:#f57c00
|
|||
|
|
style H fill:#fff3e0,stroke:#f57c00
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**峰值检测算法**(`findCuts`):
|
|||
|
|
- 对密度曲线做滑动平均平滑(kernel = minGap)
|
|||
|
|
- 以均值为参考检测峰值段(start: mean*1.05, continue: mean*0.95)
|
|||
|
|
- 在相邻峰之间的谷底确定切割位置
|
|||
|
|
- 回退机制:峰值不足时降级到阈值法(`findCutsByGap`)
|
|||
|
|
|
|||
|
|
### 固定网格(Fixed Grid)
|
|||
|
|
|
|||
|
|
当 `GridRows > 0 && GridCols > 0` 时启用,将图像等分为 Rows×Cols 个单元格。
|
|||
|
|
|
|||
|
|
- **GridPadding**:单元格间间距(默认 2px)
|
|||
|
|
- 适用于已知行列数的标准 Sprite Sheet
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔍 步骤 3:过滤 — MinFillRatio
|
|||
|
|
|
|||
|
|
切割后的每个 tile 都计算填充率:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
fillRatio = 非透明像素数 / 总像素数
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- **MinFillRatio** 默认 0.14(14%)
|
|||
|
|
- 低于阈值的 tile 被判定为空白帧,自动丢弃
|
|||
|
|
- 有效过滤因间隙检测误差产生的空白切片
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📐 步骤 4:裁剪 — trimAlpha
|
|||
|
|
|
|||
|
|
对每个 tile 执行透明边框裁剪:
|
|||
|
|
|
|||
|
|
1. 扫描四边界,找到非透明像素的最小包围矩形
|
|||
|
|
2. 裁切到该矩形,去除四周透明区域
|
|||
|
|
3. 减少冗余像素,为后续对齐做准备
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎯 步骤 5:对齐 — padToLargest
|
|||
|
|
|
|||
|
|
动画播放时,如果每帧尺寸不同且内容未对齐,会导致角色"抖动"。
|
|||
|
|
|
|||
|
|
**底部居中锚定**(Bottom-Center Anchor):
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
canvasW = maxW * 110% // 最大帧宽度 + 10% padding
|
|||
|
|
canvasH = maxH * 110% // 最大帧高度 + 10% padding
|
|||
|
|
|
|||
|
|
每帧偏移:
|
|||
|
|
ox = (canvasW - frameW) / 2 // 水平居中
|
|||
|
|
oy = canvasH - frameH // 底部对齐(脚踏同一水平线)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- 所有帧共享统一画布尺寸
|
|||
|
|
- 水平居中保证角色在同一屏幕位置
|
|||
|
|
- 底部对齐保证角色"脚踏实地",防止上下漂移
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎬 GIF Maker
|
|||
|
|
|
|||
|
|
`gifmaker.Encode()` 将处理后的帧序列编码为动画 GIF:
|
|||
|
|
|
|||
|
|
| 特性 | 实现 |
|
|||
|
|
|------|------|
|
|||
|
|
| 统一画布 | 所有帧归一化到 maxW × maxH |
|
|||
|
|
| 透明色 | 调色板索引 0 = 完全透明 |
|
|||
|
|
| 防鬼影 | `DisposalBackground` 每帧清除前一帧 |
|
|||
|
|
| 调色板 | 采样像素构建(每 3px 取样),最多 256 色 |
|
|||
|
|
| 循环 | `LoopCount = 0`(无限循环) |
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
anim.Disposal = append(anim.Disposal, gif.DisposalBackground)
|
|||
|
|
anim.BackgroundIndex = 0 // 透明色
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> ⚠️ **DisposalBackground 的重要性**:如果不设置此选项,GIF 播放器会在前一帧基础上叠加新帧,产生"残影"效果。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📦 Options 配置速查
|
|||
|
|
|
|||
|
|
| 参数 | 类型 | 默认值 | 说明 |
|
|||
|
|
|------|------|--------|------|
|
|||
|
|
| `WhiteBg` | bool | true | 白底移除模式 |
|
|||
|
|
| `WhiteThreshold` | uint8 | 40 | 白色阈值(0-255) |
|
|||
|
|
| `GreenScreen` | bool | false | 绿幕移除模式 |
|
|||
|
|
| `GreenTolerance` | float64 | 0.2 | 绿色容差(0-1) |
|
|||
|
|
| `GridRows` / `GridCols` | int | 0 | 固定网格行列数 |
|
|||
|
|
| `GapThreshold` | float64 | 0.03 | 间隙判定阈值 |
|
|||
|
|
| `MinGapWidth` | int | 2 | 最小间隙宽度(px) |
|
|||
|
|
| `MinFillRatio` | float64 | 0.14 | 最小填充率 |
|
|||
|
|
| `Trim` | bool | true | 透明边框裁剪 |
|
|||
|
|
| `CenterAlign` | bool | true | 底部居中对齐 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔗 关联文档
|
|||
|
|
|
|||
|
|
- [← 返回索引](00-index.md)
|
|||
|
|
- [05 — 生成管线](05-generation-pipeline.md) — 管线中 SplitSprite 节点的调用方
|
|||
|
|
- [08 — SSE 实时推送](08-sse-push.md) — 处理进度的实时推送
|