spritefusion-pixel-snapper — 完整教學
把 AI 生成的「不工整 pixel art」對齊到真正的整數 grid,並量化到限定色盤;單檔 870 行 Rust,可同時編譯成 CLI 與 WASM 模組。
1. 專案定位
1.1 一句話定位
Pixel Snapper 是 Sprite Fusion(線上 tilemap editor)團隊釋出的 AI pixel art 後處理工具。它接收一張 AI 生成、解析度通常為 1024×1024 的「看起來像 pixel art 但 pixel 邊界其實亂飄」的圖,輸出一張 pixel 大小一致、grid 對齊、色盤受限的乾淨 sprite。
1.2 解決什麼痛點
AI image model(GPT Image、Nano Banana、SD 系列)在生成 pixel art 時三個老問題:
- pixel 大小不一致:肉眼看像 16×16 grid,實際上每個「格子」可能是 15、16、17 個 source pixel 寬窄。
- grid 漂移:圖的左半邊 pixel 大小是 16,右半邊變成 15,整張圖沒有單一一致的 grid base。
- 色盤亂跳:同一個物件的同一塊顏色,被 model 渲染出十幾個相近但不同的 RGB 值,導致縮放後出現雜色。
直接把這種圖丟進遊戲 engine 會看到鋸齒、模糊、scaling artifact。Pixel Snapper 解決方法是:偵測真正的 grid 週期 → snap 每個 pixel 到 grid cell → 對 cell 內顏色做 k-means 量化。
1.3 為什麼選這個工具
- 單檔 Rust(870 行),無外部 service / 無雲端依賴,可離線跑。
- 同時提供 native CLI 與 WASM build target(官方 web demo 即為 WASM 版)。
- MIT license + Sprite Fusion 商業團隊背書(spritefusion.com 是付費 tilemap editor 廠商)。
- 1 個檔案 = 1 個 binary,導入 game asset pipeline 阻力極低。
2. 安裝指南
2.1 系統需求
- Rust toolchain(cargo),建議 ≥ 1.70。
- 若要 WASM build:另裝
wasm-pack。 - 無 Python、無 node、無 GPU 需求。
2.2 CLI 安裝
1git clone https://github.com/Hugo-Dz/spritefusion-pixel-snapper.git
2cd spritefusion-pixel-snapper
3cargo build --release
4# binary 位置:target/release/spritefusion-pixel-snapper
或直接用 cargo run:
1cargo run --release -- input.png output.png 16 --pixel-size 8
2.3 WASM 建置
1cargo install wasm-pack
2wasm-pack build --target web --out-dir pkg --release
3# 產出:pkg/spritefusion_pixel_snapper.js + .wasm
嵌入網頁:
1import init, { process_image } from "./pkg/spritefusion_pixel_snapper.js";
2await init();
3const outputBytes = process_image(inputBytes, 16, null);
第三參數為 pixel_size_override,傳 null 走自動偵測。
3. 核心架構解析
3.1 演算法 pipeline
整個 process_image_bytes_common()(src/main.rs L102)的流程:
flowchart TD
A[Input PNG bytes] --> B[image.load_from_memory]
B --> C[validate_image_dimensions]
C --> D[quantize_image - k-means++ 色彩量化]
D --> E[compute_profiles - 計算 X/Y 軸投影輪廓]
E --> F[estimate_step_size - 偵測 grid 週期]
F --> G{step 偵測成功?}
G -->|皆失敗| H[fallback 用 fallback_target_segments]
G -->|單軸失敗| I[用 sibling 軸 step]
G -->|皆成功| J[直接使用]
H --> K[walk - 1D walker 找切割線]
I --> K
J --> K
K --> L[stabilize_both_axes 雙軸交叉校驗]
L --> M[resample 重新取樣到 grid]
M --> N[image.write_to PNG]
N --> O[Output PNG bytes]
3.2 模組分工
| 函式 | 行範圍 | 職責 |
|---|---|---|
parse_args() | L196–243 | CLI 參數解析(input / output / k-colors / --pixel-size) |
validate_image_dimensions() | L262 | 防呆:圖太小或非法尺寸 |
quantize_image() | L276 | k-means++ 初始化 + Lloyd iteration,把所有 RGB 壓到 K 色 |
compute_profiles() | L416 | 沿 X 軸 / Y 軸算 luminance 差異累積,得到 1D 訊號 |
estimate_step_size() | L458 | 從 1D 訊號用 peak detection 估 grid 週期 |
resolve_step_sizes() | L502 | 雙軸 fallback 策略(單軸失敗用 sibling,雙軸失敗用 fallback) |
walk() | L610 | 1D walker,從第一個 peak 開始按 step_size 跳,找所有切割線 |
stabilize_both_axes() | L537 | 雙軸交叉驗證(先 raw cuts 再 cross-validate) |
stabilize_cuts() | L658 | 單軸切割線穩定化 |
snap_uniform_cuts() | L736 | fallback:強制均勻切 N 段 |
resample() | L817 | 用 col_cuts / row_cuts 從原圖採樣,輸出 grid_w × grid_h 圖 |
3.3 Config 結構
1pub struct Config {
2 pub k_colors: usize, // 預設 16
3 pub pixel_size_override: Option<f64>, // None = 自動偵測
4 k_seed: u64, // k-means++ 種子
5 max_kmeans_iterations: usize,
6 peak_threshold_multiplier: f64,
7 peak_distance_filter: usize,
8 walker_search_window_ratio: f64,
9 walker_min_search_window: f64,
10 walker_strength_threshold: f64,
11 min_cuts_per_axis: usize,
12 fallback_target_segments: usize,
13 max_step_ratio: f64,
14 // + input_path / output_path(僅 CLI 用)
15}
所有閾值都集中在 Config,讓使用者只需暴露 k_colors 與 pixel_size_override 兩個外部旋鈕。
4. CLI 詳細用法
4.1 最小命令
1cargo run --release -- input.png output.png
2# k_colors 預設 16
3# pixel_size 自動偵測
4.2 指定 k_colors
1cargo run --release -- input.png output.png 32
2# 第三個位置參數即 k_colors
4.3 覆寫 pixel size
1cargo run --release -- input.png output.png 16 --pixel-size 8
2# 強制把每 8×8 source pixel 視為一個 grid cell
--pixel-size 的合法範圍是 [1, min(width, height) / 2],傳超出範圍會回 InvalidInput。
4.4 自動偵測失敗的訊號
執行時印出:
1Pixel size: 8.0px (auto-detected)
2Output size: 128x128
若 Pixel size: 8.0px (override) 表示走 fallback;若 stdout 顯示 grid 跟你預期差很多,加 --pixel-size N 強制覆寫。
5. 應用場景
5.1 AI 生成 sprite 後處理
最直接的場景:把 GPT Image / Nano Banana 生成的 1024×1024 「pixel art-ish」圖丟進去,輸出 64×64 或 128×128 乾淨 sprite,可直接進 Unity / Godot / GB Studio。
5.2 自動化 batch pipeline
process_image() 既是 WASM entry 也可在 Rust 內部直接呼叫。寫個外層 Rust binary 把整個資料夾的圖串成 batch:
1for entry in fs::read_dir("./sprites_in")? {
2 let bytes = fs::read(entry?.path())?;
3 let out = process_image_bytes_common(&bytes, None)?;
4 fs::write(out_path, out)?;
5}
5.3 整合到 web tool
Sprite Fusion 自家的線上 editor 就是把 WASM build 包進 React/Svelte 前端,使用者拖檔即可看到結果。任何想加「snap to grid」按鈕的 web 工具都可以複製這條路徑。
5.4 不適合的場景
- 高解析度寫實風 AI 圖(不是 pixel art-ish),snap 完會變得「完全認不出」(issue #9 真實案例)。
- 已經乾淨的 pixel art:snap 沒幫助,反而可能把細節抹掉。
- 透明度有複雜 alpha gradient 的 sprite:演算法把
a == 0視為背景跳過,半透明邊緣可能不穩。
6. 資安掃描報告
6.1 結論:🟢 低風險
針對全 repo(870 行 Rust + 32 行 .gitignore + Cargo.toml)做 grep -rnE "eval|exec|os.system|subprocess|shell=True|curl|wget|http|urlopen|requests|pickle|__import__|input\(|raw_input|secret|token|password|api_key|API_KEY|unsafe",僅 1 個 match:
1src/main.rs:319: // Maybe try a faster algorithm for this? like https://crates.io/crates/kmeans_colors
純註解中的參考連結,無實際網路呼叫、無 secret、無 shell exec、無 unsafe block。
6.2 風險紅黃綠燈
| 類別 | 等級 | 說明 |
|---|---|---|
| 任意命令執行(shell / exec) | 🟢 低 | 完全無 Command::spawn / std::process / shell escape |
| 網路請求 | 🟢 低 | 無 reqwest / hyper / tokio-net 依賴;純檔案 IO |
| 機密外洩 | 🟢 低 | 無 API key / token / env var 讀取(除 env::args 取 CLI 參數) |
unsafe Rust | 🟢 低 | 全檔無 unsafe block;wasm-bindgen 暴露介面用標準 #[wasm_bindgen] |
| 依賴鏈 supply chain | 🟢 低 | 依賴僅 5 個(image / rand / rand_distr / rand_chacha / wasm-bindgen),皆為 crates.io 知名套件 |
| 路徑遍歷 / 任意讀寫 | 🟡 中(CLI mode) | CLI 直接用 env::args 接 input/output path 傳給 image::open 與 image::save;若整合到網頁後端,需自行做路徑白名單 |
| Image parsing DoS | 🟡 中 | image::load_from_memory 接外部 bytes;超大圖 / malformed PNG 可能耗 RAM 或 panic(雖有 validate_image_dimensions 防呆,但未限制 max pixel count) |
| WASM 沙箱 | 🟢 低 | 全部運算在 wasm sandbox,無法逃逸到 host |
6.3 整合到內部 pipeline 的建議
- 本地 CLI 用:直接使用,無需額外加固。
- 整合到 web service 後端:必須在外層加(a)檔案大小上限、(b)pixel 總數上限、(c)路徑白名單、(d)timeout。
- 整合到瀏覽器前端(WASM):照官方 demo 即可;WASM sandbox 已隔離。
7. FAQ
Q1:為什麼我輸出的圖完全認不出原圖? A:你的輸入可能不是「pixel art-ish」而是寫實圖。Pixel Snapper 不是 pixelate filter,是 snap 工具。先用 prompt 引導 AI 生成「明顯像素風」圖再丟進來。
Q2:可以處理透明背景嗎?
A:可以(PR #2 已支援)。但複雜 alpha gradient(半透明邊緣)可能不穩;演算法只看 a == 0 跳過,不對中間 alpha 做特別處理。
Q3:max k-colors 是多少? A:CLI 無上限(受記憶體限制);官方 web 版曾被反映「32 太少」,後來已上調。
Q4:為什麼自動偵測 grid 大小不準?
A:通常是 source 圖太「乾淨」沒有明顯 luminance peak,或太「髒」雜訊壓過 peak。對策:用 --pixel-size N 手動指定。
Q5:可以做 batch / pipeline 嗎?
A:可以。process_image_bytes_common() 是純 in-memory 函式,外層自己包 batch loop。或寫個 shell for f in *.png; do cargo run ... -- $f out/$f; done。
Q6:WASM build 多大? A:release build 約 ~200KB gzipped(image crate + rand 系列 + bindgen overhead)。
8. 進階技巧
8.1 調整 Config 內部閾值
Config 內 10+ 個非公開欄位(max_kmeans_iterations / peak_threshold_multiplier / …)控制演算法精度。要試不同設定需 fork 改 Default::default()。
建議優先調的兩個:
max_kmeans_iterations:預設 10。圖太花需提高到 20–30 換更穩定色盤。peak_threshold_multiplier:grid 偵測敏感度,調低可偵測更細的 grid。
8.2 用 k_seed 控制可重現性
k_seed 固定種子 → 同樣 input 永遠得到同樣 output。對 CI / regression test 很重要。
8.3 投影輪廓 debug
compute_profiles() 回傳 (profile_x, profile_y) 兩個 Vec<f64>。fork 一份把它 dump 成 CSV,畫圖即可直觀看出 grid 偵測在哪裡失敗。
9. 整合進其他工作流
9.1 與本專案的 layer 關係
| 場景 | 建議走法 |
|---|---|
| 拿到一張 AI sprite 想清理 | 本工具(CLI mode) |
| 想做 AI sprite batch 工作流 | 本工具 + 自寫 Rust outer batch |
| 想做 web demo | 本工具 WASM build + 前端 |
想存進 inbox/ 當參考 | 本 tutorial 已存;後續案例用 ai-gh-save / gh-tutorial-qd 收錄變體實作 |
9.2 與 Sprite Fusion 生態
Sprite Fusion 還有:tilemap editor(付費 web app)、各種 engine exporter(Unity / Godot / Defold / GB Studio)。完整 sprite pipeline 是:「AI 生圖 → Pixel Snapper(本工具)→ Sprite Fusion editor 切片排版 → engine import」。
10. 重點摘要 Checklist
- 870 行單檔 Rust,演算法乾淨可讀
- 同時支援 CLI 與 WASM
- MIT license,可商用
- 資安掃描 🟢 低風險(無網路、無 shell、無 unsafe)
- 已知不適用:寫實風 AI 圖、複雜 alpha gradient
- 主要旋鈕只有兩個:
k_colors與--pixel-size - 自動偵測失敗時有雙軸 fallback + 強制均勻 fallback
- 與 Sprite Fusion 編輯器生態整合度高(同團隊出品)
11. 進一步閱讀
- 官方 web demo:https://spritefusion.com/pixel-snapper
- Sprite Fusion 主站:https://spritefusion.com
- 作者 Hugo Duprez 個人站:https://www.hugoduprez.com/
- k-means++ 原始論文:Arthur & Vassilvitskii (2007), “k-means++: The Advantages of Careful Seeding”
- 替代色彩量化(作者註解中提到):https://crates.io/crates/kmeans_colors
- wasm-pack 文件:https://rustwasm.github.io/wasm-pack/
Comments