Files
cs-note/hzh/Gen2D/06-精灵图处理.md
T

215 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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) — 处理进度的实时推送