7.0 KiB
tags, create time
| tags | create time | ||||||
|---|---|---|---|---|---|---|---|
|
2026-06-03 10:25 |
06. 精灵图处理管线
概述
自动精灵图处理管线 — 背景移除 → 投影检测 → 切割 → 对齐 → GIF 预览,将一张 AI 生成的 Sprite Sheet 无缝转化为可用的逐帧动画资源。
正文
flowchart LR
A["Input PNG"] --> B["Background Removal"]
B --> C["Gap Detection"]
C --> D["Tile Extract"]
D --> E["Filter MinFill"]
E --> F["Trim Alpha"]
F --> G["Align padToLargest"]
G --> H["GIF 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)
// 阈值距离 #FFFFFF + alpha 渐变
dist := max(255-R, max(255-G, 255-B))
if dist < threshold/2 → 全透明
if dist < threshold → alpha 线性渐变(抗锯齿)
- WhiteThreshold 默认 40,距离纯白 40 以内的像素被处理
- 采用渐变 alpha 实现平滑过渡,避免硬边缘
绿幕模式(GreenScreen)
// 绿色主导检测
gDominance := G - (R+B)/2
if gDominance > tolerance*255 → 透明化
- GreenTolerance 默认 0.2,控制绿色检测灵敏度
- 适用于绿色背景的 AI 生成图
[!tip] 设计选择 两种模式互斥,
Process()根据opts.WhiteBg/opts.GreenScreen自动选择。
步骤 2:切割策略
管线提供两种切割方式,根据配置自动切换:
投影检测(Projection Detection)
这是 Gen2D 的核心创新点。传统全局投影在检测列边界时,会因为武器、尾巴、翅膀等突出物被"稀释"而导致切割不准。
两阶段投影算法:
-
全局行投影:统计每行非透明像素占比,检测水平间隙
- 行间隙通常是干净且全宽的,全局投影效果好
-
逐行列投影:在每个行段内独立计算列密度
- 防止突出物(武器/尾巴)被其他行稀释
- 每行段获得独立的列边界,互不干扰
[!question] 为什么不用简单的阈值分割?
精灵图中的角色往往有复杂轮廓——比如挥舞的剑可能横跨多个帧的位置。如果仅用固定阈值,剑的连续像素会让算法误判为一帧。按行段独立分析的思路是把二维问题拆解为多个一维子问题,每个子问题只关心当前行段的内容,从而避免跨行干扰。
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: mean1.05, continue: mean0.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 执行透明边框裁剪:
- 扫描四边界,找到非透明像素的最小包围矩形
- 裁切到该矩形,去除四周透明区域
- 减少冗余像素,为后续对齐做准备
步骤 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(无限循环) |
anim.Disposal = append(anim.Disposal, gif.DisposalBackground)
anim.BackgroundIndex = 0 // 透明色
[!warning] 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-索引 — 文档导航与架构总览图
- 05-生成管线 — 管线中 SplitSprite 节点的调用方
- 08-SSE实时推送 — 处理进度的实时推送