sprite-slicer 0.1.2

Sprite-sheet slicing, transparent sprite detection, action grouping, background removal, frame normalization, and GIF preview export.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
# sprite-slicer

`sprite-slicer` 是一个面向 2D 游戏素材流程的 Rust crate,既能作为命令行工具使用,也能作为库嵌入到你自己的工具链中。

它解决的是一条完整的 sprite 处理链路:

- 把规则 sprite sheet 按网格切成单帧
- 从透明背景图中自动检测单帧
- 按动作把帧重新分组
- 去除黑色背景并转透明 PNG
- 把一组帧统一到同一画布和锚点
- 导出 GIF 预览动画

仓库地址:

- GitHub: <https://github.com/mdddj/sprite-slicer>
- docs.rs: <https://docs.rs/sprite-slicer>

## 适用场景

- AI 生成了一整张角色动作图,需要切成单帧
- 一张图里角色站位不规则,需要自动检测角色块
- 想把帧按 `idle / walk / attack / hurt` 分到不同目录
- 想把黑底或近黑底图片转成透明背景 PNG
- 想把动作帧对齐到统一尺寸,避免游戏里角色抖动
- 想快速导出 GIF 看动画节奏是否正常

## 特性

- 同时提供 `CLI``library API`
- 输入输出都是文件和目录,适合脚本、工具链、AI agent 调用
- 支持规则网格切图和透明图自动检测
- 支持动作分组导出
- 支持背景去除和帧规范化
- 支持 GIF 预览导出
- 适合像素角色、敌人动画、JRPG sprite workflow

## 安装

作为库:

```bash
cargo add sprite-slicer
```

作为命令行工具:

```bash
cargo install sprite-slicer
```

## 目录约定

这个库是“文件导向”的。大多数 API 都会:

- 从一个图片文件或目录读入
- 在输出目录里生成 PNG、TOML、TXT、GIF
- 返回一个包含产物路径和统计信息的结果结构体

默认产物通常包括:

- `frames/`:切出来的 PNG 单帧
- `frames.toml`:帧清单 manifest
- `index-map.txt`:便于肉眼核对帧序号的文本图
- `actions.toml`:动作分组后的摘要清单

## 给 AI 调用的建议

如果你后续要让别的 AI 直接操作这个库,建议固定用下面这套顺序:

1. 如果原图是黑底或纯色底,先调用 `remove_background`
2. 如果是规则网格图,调用 `slice_sheet`
3. 如果不是规则网格、已经是透明底,调用 `detect_frames`
4. 根据 `frames.toml` 生成动作配置,再调用 `group_actions`
5. 对每个动作目录调用 `normalize_frames`
6. 对每个动作目录调用 `export_gif` 做预览

建议 AI 遵循这些约束:

- 所有输入路径都使用绝对路径
- 输出目录不要复用未清理的旧结果目录
- `group_actions` 之前必须确保 `frames.toml` 已生成
- `normalize_frames``export_gif` 只接受 PNG 文件或只包含 PNG 的目录
- 对 AI 生成图优先尝试 `detect_frames`,对规则表格图优先尝试 `slice_sheet`

## AI 调用模板

下面这段可以直接复制给别的 AI,当作它操作这个库的规则。

```text
你正在操作一个 Rust 库:sprite-slicer。

目标:
- 处理 2D sprite sheet
- 产出单帧 PNG、动作目录、规范化帧和 GIF 预览

公开函数:
- slice_sheet(SliceOptions) -> Result<SliceOutput>
- detect_frames(DetectOptions) -> Result<DetectOutput>
- group_actions(GroupOptions) -> Result<GroupOutputSummary>
- normalize_frames(NormalizeOptions) -> Result<NormalizeOutput>
- export_gif(GifOptions) -> Result<GifOutput>
- remove_background(RemoveBgOptions) -> Result<RemoveBgOutput>

工作流规则:
1. 如果输入图是黑底或纯色背景,先调用 remove_background。
2. 如果图像是规则网格,调用 slice_sheet。
3. 如果图像不是规则网格但背景透明,调用 detect_frames。
4. 必须根据 frames.toml 中的 frame index 来生成 group_actions 的配置。
5. 动作分组完成后,对每个动作目录调用 normalize_frames。
6. 预览动画时,对规范化后的动作目录调用 export_gif。

路径规则:
- 输入输出路径尽量使用绝对路径。
- 不要把不同步骤的结果写到同一个未清理目录。
- normalize_frames 和 export_gif 的输入必须是 PNG 文件或仅包含 PNG 的目录。
- 目录输入时,这个库只扫描当前目录下一层 PNG,不做递归扫描。

参数选择建议:
- 黑底去背:bg_hex = "#000000",threshold 从 8 开始尝试。
- AI 生成图自动检测:min_opaque_pixels 从 64 开始,padding 从 2 开始,row_tolerance 从 24 开始。
- 角色动画对齐:anchor_x = Center,anchor_y = Bottom,pad = 2 或 4。
- GIF 预览:fps = 8 或 12,repeat = 0。

错误处理建议:
- 如果 detect_frames 报 no components matched,降低 min_opaque_pixels,或调整 alpha/background threshold。
- 如果 group_actions 报 frame index 不存在,检查动作配置里的索引是否来自 frames.toml。
- 如果 group_actions 报 frame was not exported,说明切图时 skip_empty 把该帧跳过了。
- 如果 export_gif 或 normalize_frames 报 no png frames found,检查输入目录是否真的有 PNG。
```

## 公开 API 总览

当前公开函数一共 6 个:

- `slice_sheet(options: SliceOptions) -> Result<SliceOutput>`
- `detect_frames(options: DetectOptions) -> Result<DetectOutput>`
- `group_actions(options: GroupOptions) -> Result<GroupOutputSummary>`
- `export_gif(options: GifOptions) -> Result<GifOutput>`
- `remove_background(options: RemoveBgOptions) -> Result<RemoveBgOutput>`
- `normalize_frames(options: NormalizeOptions) -> Result<NormalizeOutput>`
- `process_sprite_sheet(options: ProcessSheetOptions) -> Result<ProcessSheetOutput>`

可直接引入:

```rust
use sprite_slicer::{
    detect_frames, export_gif, group_actions, normalize_frames, process_sprite_sheet,
    remove_background, slice_sheet, AnchorX, AnchorY, ComponentMode, DetectOptions, FrameAlign,
    GifOptions, GroupOptions, NormalizeOptions, ProcessSheetOptions, RemoveBgOptions,
    SliceOptions,
};
```

## 快速示例

### 1. 黑底转透明

```rust
use std::path::PathBuf;

use sprite_slicer::{remove_background, RemoveBgOptions};

fn main() -> anyhow::Result<()> {
    let output = remove_background(RemoveBgOptions {
        input: PathBuf::from("sheet-black.png"),
        output: PathBuf::from("sheet-transparent.png"),
        bg_hex: "#000000".to_string(),
        threshold: 8,
        alpha_threshold: 0,
    })?;

    println!("removed {} pixels", output.removed_pixels);
    Ok(())
}
```

### 2. 规则网格切图

```rust
use std::path::PathBuf;

use sprite_slicer::{slice_sheet, SliceOptions};

fn main() -> anyhow::Result<()> {
    let output = slice_sheet(SliceOptions {
        input: PathBuf::from("hero-sheet.png"),
        output: PathBuf::from("out/slice"),
        frame_width: 64,
        frame_height: 64,
        columns: Some(8),
        rows: Some(6),
        offset_x: 0,
        offset_y: 0,
        gap_x: 0,
        gap_y: 0,
        skip_empty: true,
        alpha_threshold: 0,
        min_opaque_pixels: 1,
        bg_hex: None,
        bg_threshold: 0,
        manifest_name: "frames.toml".to_string(),
    })?;

    println!("manifest: {}", output.manifest_path.display());
    println!("index map: {}", output.index_map_path.display());
    println!("frame count: {}", output.frame_count);
    println!("kept frames: {}", output.kept_frames);
    Ok(())
}
```

### 3. 透明图自动检测

```rust
use std::path::PathBuf;

use sprite_slicer::{detect_frames, DetectOptions};

fn main() -> anyhow::Result<()> {
    let output = detect_frames(DetectOptions {
        input: PathBuf::from("sheet-transparent.png"),
        output: PathBuf::from("out/detect"),
        alpha_threshold: 0,
        min_opaque_pixels: 64,
        padding: 2,
        row_tolerance: 24,
        bg_hex: None,
        bg_threshold: 0,
        manifest_name: "frames.toml".to_string(),
    })?;

    println!("detected frames: {}", output.detected_frames);
    println!("rows: {}", output.rows);
    Ok(())
}
```

### 4. 按动作分组

```rust
use std::path::PathBuf;

use sprite_slicer::{group_actions, GroupOptions};

fn main() -> anyhow::Result<()> {
    let output = group_actions(GroupOptions {
        manifest: PathBuf::from("out/detect/frames.toml"),
        config: PathBuf::from("examples/actions.toml"),
        output: PathBuf::from("out/actions"),
    })?;

    println!("actions manifest: {}", output.manifest_path.display());
    for action in output.actions {
        println!("{} -> {}", action.name, action.frame_count);
    }
    Ok(())
}
```

### 5. 统一帧尺寸和锚点

### 6. 洋红底 sprite sheet 后处理

这个入口更接近 AI 生成 sprite 的实际落地流程:去洋红背景、切网格、按最大主体或整帧裁剪、统一缩放、重新拼 sheet、导出 GIF,并输出 `pipeline-meta.json`。

```rust
use std::path::PathBuf;

use sprite_slicer::{process_sprite_sheet, ComponentMode, FrameAlign, ProcessSheetOptions};

fn main() -> anyhow::Result<()> {
    let output = process_sprite_sheet(ProcessSheetOptions {
        input: PathBuf::from("raw-sheet.png"),
        output_dir: PathBuf::from("out/processed"),
        rows: 2,
        cols: 2,
        cell_size: 128,
        bg_hex: "#FF00FF".to_string(),
        threshold: 100,
        edge_threshold: 150,
        fit_scale: 0.85,
        trim_border: 4,
        edge_clean_depth: 3,
        align: FrameAlign::Center,
        shared_scale: true,
        component_mode: ComponentMode::All,
        component_padding: 0,
        min_component_area: 1,
        edge_touch_margin: 2,
        reject_edge_touch: true,
        gif_delay: 20,
        frame_labels: None,
        prompt: Some("forest spell impact".to_string()),
    })?;

    println!("sheet: {}", output.sheet_path.display());
    println!("gif: {}", output.gif_path.display());
    println!("meta: {}", output.metadata_path.display());
    println!("frames: {}", output.frame_count);
    println!("edge-touch frames: {:?}", output.edge_touch_frames);
    Ok(())
}
```

```rust
use std::path::PathBuf;

use sprite_slicer::{normalize_frames, AnchorX, AnchorY, NormalizeOptions};

fn main() -> anyhow::Result<()> {
    let output = normalize_frames(NormalizeOptions {
        input: PathBuf::from("out/actions/walk"),
        output: PathBuf::from("out/normalized/walk"),
        width: None,
        height: None,
        anchor_x: AnchorX::Center,
        anchor_y: AnchorY::Bottom,
        pad: 4,
    })?;

    println!("canvas: {}x{}", output.canvas_width, output.canvas_height);
    println!("frames: {}", output.frame_count);
    Ok(())
}
```

### 6. 导出 GIF 预览

```rust
use std::path::PathBuf;

use sprite_slicer::{export_gif, GifOptions};

fn main() -> anyhow::Result<()> {
    let output = export_gif(GifOptions {
        input: PathBuf::from("out/normalized/walk"),
        output: PathBuf::from("out/previews/walk.gif"),
        fps: 8,
        repeat: 0,
        pad: 4,
    })?;

    println!("gif: {}", output.output_path.display());
    println!("frames: {}", output.frame_count);
    Ok(())
}
```

## API 详细说明

### `slice_sheet`

签名:

```rust
pub fn slice_sheet(options: SliceOptions) -> anyhow::Result<SliceOutput>
```

用途:

- 适合规则网格的 sprite sheet
- 按固定宽高、固定行列切出单帧
- 可跳过空白格

`SliceOptions` 字段说明:

- `input`: 输入图片路径
- `output`: 输出目录
- `frame_width`: 单帧宽度
- `frame_height`: 单帧高度
- `columns`: 列数;`None` 时自动推导
- `rows`: 行数;`None` 时自动推导
- `offset_x`: 左侧起始偏移
- `offset_y`: 顶部起始偏移
- `gap_x`: 横向间距
- `gap_y`: 纵向间距
- `skip_empty`: 是否跳过空白帧导出
- `alpha_threshold`: alpha 小于等于该值时当作透明
- `min_opaque_pixels`: 前景像素少于该值时视为空帧
- `bg_hex`: 可选背景色,形如 `#000000`
- `bg_threshold`: 背景颜色容差
- `manifest_name`: 输出 manifest 文件名,通常是 `frames.toml`

输出 `SliceOutput`:

- `manifest_path`: manifest 路径
- `index_map_path`: 文本索引图路径
- `frame_count`: 总格子数
- `kept_frames`: 实际导出的帧数

会生成:

- `output/frames/*.png`
- `output/<manifest_name>`
- `output/index-map.txt`

适合 AI 的判断规则:

- 如果图片是标准行列排布,用它
- 如果帧之间不规则、大小不一致,不要用它

### `detect_frames`

签名:

```rust
pub fn detect_frames(options: DetectOptions) -> anyhow::Result<DetectOutput>
```

用途:

- 适合透明背景图
- 用连通区域检测方式识别每个 sprite
- 自动按行聚类并编号

`DetectOptions` 字段说明:

- `input`: 输入图片路径
- `output`: 输出目录
- `alpha_threshold`: alpha 小于等于该值时视为空气
- `min_opaque_pixels`: 小于该前景像素数的连通块会被忽略
- `padding`: 对检测框四周补边
- `row_tolerance`: 自动分行时允许的垂直容差
- `bg_hex`: 可选背景色;即使不是透明图,也可配合颜色过滤
- `bg_threshold`: 背景颜色容差
- `manifest_name`: 输出 manifest 文件名

输出 `DetectOutput`:

- `manifest_path`: manifest 路径
- `index_map_path`: 文本索引图路径
- `detected_frames`: 检测到的帧数
- `rows`: 聚类出的行数

会生成:

- `output/frames/*.png`
- `output/<manifest_name>`
- `output/index-map.txt`

适合 AI 的判断规则:

- AI 生成图优先尝试这个函数
- 如果报 `no components matched`,通常要降低 `min_opaque_pixels` 或调整阈值

### `group_actions`

签名:

```rust
pub fn group_actions(options: GroupOptions) -> anyhow::Result<GroupOutputSummary>
```

用途:

- 根据动作配置,把切好的帧复制到各自动作目录
- 适合把一堆 `frames/*.png` 重新组织成 `idle/ walk/ attack/`

`GroupOptions` 字段说明:

- `manifest`: `slice_sheet``detect_frames` 产出的 `frames.toml`
- `config`: 动作配置 TOML 文件
- `output`: 动作输出目录

动作配置格式:

```toml
[[actions]]
name = "idle"
frames = [0, 1, 2, 3]

[[actions]]
name = "walk"
frames = [4, 5, 6, 7]
```

输出 `GroupOutputSummary`:

- `manifest_path`: 输出的 `actions.toml`
- `actions`: 每个动作的摘要列表

每个摘要项 `GroupedActionSummary` 包含:

- `name`: 动作名
- `frame_count`: 帧数量

会生成:

- `output/<action-name>/0000.png`
- `output/<action-name>/0001.png`
- `output/actions.toml`

注意:

- `frames` 里填的是 `frames.toml` 里的帧索引,不是文件名
- 如果某帧在切图阶段因为 `skip_empty` 没导出,这里会报错

### `normalize_frames`

签名:

```rust
pub fn normalize_frames(options: NormalizeOptions) -> anyhow::Result<NormalizeOutput>
```

用途:

- 把一组 PNG 帧放到同样大小的画布上
- 用统一锚点对齐,减少游戏里跳动感

`NormalizeOptions` 字段说明:

- `input`: 输入 PNG 文件或目录
- `output`: 输出目录
- `width`: 目标宽度;`None` 时使用输入帧中的最大宽度
- `height`: 目标高度;`None` 时使用输入帧中的最大高度
- `anchor_x`: 水平锚点,支持 `Left / Center / Right`
- `anchor_y`: 垂直锚点,支持 `Top / Center / Bottom`
- `pad`: 每边额外留白

输出 `NormalizeOutput`:

- `output_dir`: 输出目录
- `frame_count`: 输出帧数
- `canvas_width`: 最终画布宽度
- `canvas_height`: 最终画布高度
- `anchor_x`: 实际使用的水平锚点
- `anchor_y`: 实际使用的垂直锚点

输入规则:

- 传文件时,必须是 PNG
- 传目录时,只会扫描该目录下一层的 PNG,不会递归

适合游戏导入的默认建议:

- 角色动画推荐 `AnchorX::Center + AnchorY::Bottom`
- 顶视角特效可考虑 `Center + Center`

### `export_gif`

签名:

```rust
pub fn export_gif(options: GifOptions) -> anyhow::Result<GifOutput>
```

用途:

- 从单个 PNG 或一个 PNG 目录生成 GIF 预览
- 方便先肉眼检查动作节奏

`GifOptions` 字段说明:

- `input`: 输入 PNG 文件或目录
- `output`: 输出 GIF 路径
- `fps`: 帧率,最小按 1 处理
- `repeat`: 重复次数,`0` 表示无限循环
- `pad`: 给 GIF 画布四周加留白

输出 `GifOutput`:

- `output_path`: GIF 路径
- `frame_count`: 帧数
- `canvas_width`: GIF 画布宽度
- `canvas_height`: GIF 画布高度
- `fps`: 实际使用的帧率

输入规则:

- 传目录时,只读取该目录下一层的 PNG
- 文件名会先排序,再按顺序组成 GIF

建议:

- 最好先用 `normalize_frames`,再导出 GIF
- 这样每帧尺寸一致,预览更稳定

### `remove_background`

签名:

```rust
pub fn remove_background(options: RemoveBgOptions) -> anyhow::Result<RemoveBgOutput>
```

用途:

- 把和边界连通的背景色抠掉,转成透明
- 适合黑底、纯色底、近纯色底 sprite 图

`RemoveBgOptions` 字段说明:

- `input`: 输入图片路径
- `output`: 输出 PNG 路径
- `bg_hex`: 背景颜色,例如 `#000000`
- `threshold`: 颜色容差
- `alpha_threshold`: 已经接近透明的像素阈值

输出 `RemoveBgOutput`:

- `output_path`: 输出路径
- `removed_pixels`: 被清成透明的像素数

这个函数的行为不是“删除所有黑色像素”,而是:

- 从图片边界开始找背景
- 只删除和边界连通的背景区域

这样更安全,能尽量保住角色内部描边、眼睛、武器暗部这些黑色细节。

## 公开类型

对外暴露的主要类型有:

- `SliceOptions`
- `DetectOptions`
- `GroupOptions`
- `GifOptions`
- `RemoveBgOptions`
- `NormalizeOptions`
- `SliceOutput`
- `DetectOutput`
- `GroupedActionSummary`
- `GroupOutputSummary`
- `GifOutput`
- `RemoveBgOutput`
- `NormalizeOutput`
- `SliceManifest`
- `FrameRecord`
- `DetectionMode`
- `ActionSpec`
- `AnchorX`
- `AnchorY`

### `AnchorX`

支持:

- `AnchorX::Left`
- `AnchorX::Center`
- `AnchorX::Right`

字符串解析也支持:

- `"left"`
- `"center"`
- `"right"`

### `AnchorY`

支持:

- `AnchorY::Top`
- `AnchorY::Center`
- `AnchorY::Bottom`

字符串解析也支持:

- `"top"`
- `"center"`
- `"bottom"`

## Manifest 格式

`slice_sheet` 和 `detect_frames` 生成的 manifest 都是 `SliceManifest`。

关键字段:

- `source`: 原图路径
- `frame_width`
- `frame_height`
- `columns`
- `rows`
- `offset_x`
- `offset_y`
- `gap_x`
- `gap_y`
- `alpha_threshold`
- `min_opaque_pixels`
- `bg_hex`
- `bg_threshold`
- `detection`: `grid``connected_components`
- `frames`: 帧列表

`frames` 中每项是 `FrameRecord`:

- `index`: 帧索引
- `row`: 所在行
- `column`: 所在列
- `x`: 原图裁剪起点 x
- `y`: 原图裁剪起点 y
- `width`
- `height`
- `opaque_pixels`
- `kept`: 是否保留导出
- `file`: 导出的相对文件路径,可为空

示例:

```toml
source = "/abs/path/sheet.png"
frame_width = 64
frame_height = 64
columns = 4
rows = 2
offset_x = 0
offset_y = 0
gap_x = 0
gap_y = 0
alpha_threshold = 0
min_opaque_pixels = 1
bg_threshold = 0
detection = "grid"

[[frames]]
index = 0
row = 0
column = 0
x = 0
y = 0
width = 64
height = 64
opaque_pixels = 3210
kept = true
file = "frames/frame_0000_r00_c00.png"
```

## CLI 用法

### 按网格切图

```bash
sprite-slicer slice \
  --input /path/to/sheet.png \
  --output ./out/slice \
  --frame-width 64 \
  --frame-height 64 \
  --columns 8 \
  --rows 6 \
  --skip-empty
```

### 从透明图自动检测单帧

```bash
sprite-slicer detect \
  --input /path/to/sheet-transparent.png \
  --output ./out/detect \
  --min-opaque-pixels 5000 \
  --padding 4 \
  --row-tolerance 28
```

### 按动作分组

```bash
sprite-slicer group \
  --manifest ./out/slice/frames.toml \
  --config ./examples/actions.toml \
  --output ./out/actions
```

### 导出 GIF 预览

```bash
sprite-slicer gif \
  --input ./out/actions/idle \
  --output ./out/previews/idle.gif \
  --fps 8 \
  --pad 4
```

### 去除黑色背景

```bash
sprite-slicer remove-bg \
  --input ./sheet-black.png \
  --output ./sheet-transparent.png \
  --bg-hex "#000000" \
  --threshold 8
```

### 统一帧尺寸和锚点

```bash
sprite-slicer normalize \
  --input ./out/actions/idle \
  --output ./out/normalized/idle \
  --anchor-x center \
  --anchor-y bottom \
  --pad 4
```

## 推荐工作流

### 工作流 1:AI 生成黑底整图

1. `remove_background`
2. `detect_frames`
3. `group_actions`
4. `normalize_frames`
5. `export_gif`

### 工作流 2:规则像素角色表

1. `slice_sheet`
2. `group_actions`
3. `normalize_frames`
4. `export_gif`

### 工作流 3:导入游戏前整理

1. 对每个动作目录调用 `normalize_frames`
2. 把归一化后的 PNG 导入游戏引擎
3. 预览时用 `export_gif` 做检查

## 游戏导入建议

这个库本身不绑定具体引擎,但整理后的结果适合导入:

- Flutter Flame 2D
- Godot
- Cocos Creator
- Unity 2D
- 自己写的 sprite animation 系统

常见做法:

- 一个动作一个目录,如 `idle/``walk/``attack/`
- 每帧文件名按 `0000.png``0001.png` 递增
- 游戏里按目录读取并排序
- 锚点统一后,角色切动作时位置更稳定

## 依赖

- `image`
- `gif`
- `serde`
- `toml`
- `clap`
- `anyhow`

## 发布状态

当前 crate 已经是 `lib + bin` 结构,并且已通过:

```bash
cargo test
cargo check
cargo package --allow-dirty
```

发布到 crates.io:

```bash
cargo publish --registry crates-io
```