From e2a6d4345c65161a648bdf545429ad3262592afe Mon Sep 17 00:00:00 2001 From: Edward Chang Date: Wed, 13 May 2026 22:49:33 +0800 Subject: [PATCH] feat: add Sprite Merger documentation and Inset Crop Tool integration - Added comprehensive documentation for Sprite Merger tool (both Chinese and English) - Added Inset Crop Tool documentation with real-time preview feature - Updated project structure and API endpoints sections - Integrated Inset Crop Tool with background removal and intelligent cropping - Updated navigation and tool registry in tool.py - All documentation reflects both new features and complete tool suite --- README.md | 71 ++++++++ README_EN.md | 70 +++++++- inset_crop_tool.py | 403 +++++++++++++++++++++++++++++++++++++++++++++ tool.py | 4 + 4 files changed, 546 insertions(+), 2 deletions(-) create mode 100644 inset_crop_tool.py diff --git a/README.md b/README.md index e21dbaf..498aefb 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,37 @@ SpriteTool 是一個整合式的 Web 應用程式,集合了四個專業級工 --- +### 🧩 Sprite Merger(精靈合併工具) +將來源 Sprite 精確合併到主 Sprite Sheet 的指定格位。 + +**功能特性:** +- 上傳主圖和來源圖片 +- 設定主圖的行列數,自動計算格子尺寸 +- 指定目標格位座標(Column / Row) +- 自動對齊並合併來源圖到目標位置 +- 保留透明度和色彩資訊 +- 即時預覽合併結果 +- 一鍵下載完成的圖片 + +**使用場景:** 角色變體組裝、Tilemap 元素組合、Sprite Sheet 內容更新 + +--- + +### ✂️ Inset Crop Tool(內縮裁切工具) +去除 Chroma Key 底色後,對 Sprite Sheet 進行內縮裁切及放大處理。 + +**功能特性:** +- **色彩去除** - 自訂底色(預設 #ff00ff 粉紅色)及去除閾值 +- **即時預覽** - 調整 threshold 時實時顯示去底色效果 +- **智慧裁切** - 根據行列數和內縮距離精確分割每格 +- **縮放還原** - 內縮後自動放大回原格尺寸 +- **批量輸出** - 導出個別格子 PNG + 組合完整圖 +- 支援 ZIP 壓縮打包下載 + +**使用場景:** Sprite Sheet 去底色處理、像素藝術格式標準化、動畫幀序列優化 + +--- + ## 🚀 快速開始 ### 環境需求 @@ -98,6 +129,8 @@ python tool.py - **Sprite Picker**: `http://localhost:8000/picker/` - **Shiny Maker**: `http://localhost:8000/shiny/` - **Sprite Flipper**: `http://localhost:8000/flipper/` +- **Sprite Merger**: `http://localhost:8000/merger/` +- **Inset Crop Tool**: `http://localhost:8000/inset/` --- @@ -111,10 +144,15 @@ SpriteTool/ ├── shiny_maker.py # Shiny Monster Maker 子應用 ├── rotate_webtool.py # Sprite Flipper 子應用 ├── rotate.py # Flipper 核心處理邏輯 +├── sprite_merger.py # Sprite Merger 子應用 +├── inset_crop_tool.py # Inset Crop Tool 子應用 +├── remove_pink_background.py # 色彩去除工具(獨立使用) ├── output/ # Grid Tool 導出目錄 ├── shiny_output/ # Shiny Maker 導出目錄 └── README.md # 本文件 ``` +└── README.md # 本文件 +``` --- @@ -160,6 +198,30 @@ SpriteTool/ 4. **檢查結果** - 下方會顯示翻轉後的預覽圖 5. **下載** - 點擊下載按鈕取得成品 +### Sprite Merger 工作流 + +1. **上傳圖片** - 選擇主 Sprite Sheet 和來源圖片 +2. **設定主圖網格** - 輸入主圖的行列數 +3. **指定目標位置** - 輸入目標 Column 和 Row 編號(從 0 開始) +4. **檢查預覽** - 查看合併後的預覽結果 +5. **下載** - 點擊下載按鈕取得合併後的圖片 + +### Inset Crop Tool 工作流 + +1. **上傳 Sprite Sheet** - 選擇含有底色的圖片 +2. **調整去底色設定** + - 選擇或輸入底色(如 #ff00ff) + - 調整 Threshold 閾值,觀察右側實時預覽 + - 確認去底色效果滿意 +3. **設定裁切參數** + - 輸入圖片的行列數(Cols/Rows) + - 設定四邊內縮距離(px) + - 查看計算的格子尺寸及輸出數量 +4. **確認並下載** + - 點擊「確認並下載 ZIP」按鈕 + - 系統執行去底色 → 內縮裁切 → 放大還原 + - 下載包含所有格子 PNG + 完整組合圖的 ZIP 檔 + --- ## 🛠️ 技術架構 @@ -204,6 +266,15 @@ SpriteTool/ - `GET /flipper/` - 首頁 - `POST /flipper/flip` - 執行精靈翻轉處理 +### Sprite Merger +- `GET /merger/` - 首頁 +- `POST /merger/merge` - 執行精靈合併處理 + +### Inset Crop Tool +- `GET /inset/` - 首頁 +- `POST /inset/preview` - 即時預覽去底色效果 +- `POST /inset/process` - 執行去底色 + 裁切 + 打包 + --- ## 🎨 UI/UX 特性 diff --git a/README_EN.md b/README_EN.md index 4692288..efcd54b 100644 --- a/README_EN.md +++ b/README_EN.md @@ -67,6 +67,37 @@ Quickly perform horizontal flipping (mirroring) for each animation cell in a Spr --- +### 🧩 Sprite Merger +Precisely merge source Sprite into the target cell of a main Sprite Sheet. + +**Key Features:** +- Upload main image and source sprite. +- Set main image row/column counts with automatic cell size calculation. +- Specify target cell coordinates (Column / Row). +- Automatic alignment and merge of source sprite to target position. +- Preserves transparency and color information. +- Real-time preview of merge result. +- One-click download of completed image. + +**Use Cases:** Character variant assembly, Tilemap element composition, Sprite Sheet content updates. + +--- + +### ✂️ Inset Crop Tool +Remove Chroma Key background and perform intelligent inset cropping and enlargement on Sprite Sheets. + +**Key Features:** +- **Color Removal** - Custom chroma key (default #ff00ff pink) and threshold adjustment. +- **Real-time Preview** - Instantly view background removal effects while adjusting threshold. +- **Smart Cropping** - Precisely divide cells based on rows/columns and inset distance. +- **Enlarge After Crop** - Automatically enlarge back to original cell size after inset cropping. +- **Batch Export** - Export individual cell PNGs + combined full image. +- **ZIP Archive** - Download all results in a single ZIP package. + +**Use Cases:** Sprite Sheet background removal, pixel art standardization, animation frame sequence optimization. + +--- + ## 🚀 Quick Start ### Prerequisites @@ -96,8 +127,8 @@ Visit the home page to see all available tools, or access them directly: - **Sprite Picker**: `http://localhost:8000/picker/` - **Shiny Maker**: `http://localhost:8000/shiny/` - **Sprite Flipper**: `http://localhost:8000/flipper/` - ---- +- **Sprite Merger**: `http://localhost:8000/merger/` +- **Inset Crop Tool**: `http://localhost:8000/inset/` ## 📂 Project Structure @@ -109,6 +140,9 @@ SpriteTool/ ├── shiny_maker.py # Shiny Monster Maker sub-app ├── rotate_webtool.py # Sprite Flipper sub-app ├── rotate.py # Flipper core processing logic +├── sprite_merger.py # Sprite Merger sub-app +├── inset_crop_tool.py # Inset Crop Tool sub-app +├── remove_pink_background.py # Color removal tool (standalone use) ├── output/ # Grid Tool export directory ├── shiny_output/ # Shiny Maker export directory └── README.md # This file @@ -158,6 +192,30 @@ SpriteTool/ 4. **Check Result** - View the flipped preview image below. 5. **Download** - Click the download button to get the result. +### Sprite Merger Workflow + +1. **Upload Images** - Select the main Sprite Sheet and source sprite. +2. **Set Main Grid** - Enter the row and column counts for the main image. +3. **Specify Target Position** - Enter the target column and row indices (starting from 0). +4. **Preview Merge** - View the merge result preview. +5. **Download** - Click the download button to get the merged image. + +### Inset Crop Tool Workflow + +1. **Upload Sprite Sheet** - Select an image with chroma key background. +2. **Adjust Background Removal Settings** + - Choose or input the background color (e.g., #ff00ff). + - Adjust Threshold slider and observe real-time preview on the right. + - Confirm the background removal result. +3. **Configure Crop Parameters** + - Enter grid row and column counts (Cols/Rows). + - Set inset distance (px) for all edges. + - View calculated cell size and output count. +4. **Confirm and Download** + - Click "Confirm and Download ZIP" button. + - System executes: Remove background → Inset crop → Enlarge back → Package. + - Download ZIP containing all individual cell PNGs + combined image. + --- ## 🛠️ Technical Architecture @@ -202,6 +260,14 @@ SpriteTool/ - `GET /flipper/` - Home page. - `POST /flipper/flip` - Execute sprite flipping process. +### Sprite Merger +- `GET /merger/` - Home page. +- `POST /merger/merge` - Execute sprite merge process. + +### Inset Crop Tool +- `GET /inset/` - Home page. +- `POST /inset/preview` - Real-time background removal preview. +- `POST /inset/process` - Execute background removal + cropping + packaging. --- ## 🎨 UI/UX Features diff --git a/inset_crop_tool.py b/inset_crop_tool.py new file mode 100644 index 0000000..47af2c8 --- /dev/null +++ b/inset_crop_tool.py @@ -0,0 +1,403 @@ +""" +Inset Crop Tool + Pink BG Remover +1. 上傳圖片,設定底色與 threshold → 即時預覽去底色效果 +2. 確認後設定 cols / rows / inset → 下載 ZIP +""" + +from __future__ import annotations + +import io +import zipfile + +import numpy as np +from fastapi import FastAPI, File, Form, UploadFile +from fastapi.responses import HTMLResponse, PlainTextResponse, StreamingResponse +from PIL import Image + +app = FastAPI() + + +# ────────────────────────────────────────────── +# BG Removal helper (from remove_pink_background.py) +# ────────────────────────────────────────────── + +def _parse_hex(value: str) -> tuple[int, int, int]: + v = value.strip().lstrip("#") + if len(v) != 6: + raise ValueError("需要 6 位 hex 色碼,例如 #ff00ff") + return int(v[0:2], 16), int(v[2:4], 16), int(v[4:6], 16) + + +def remove_background(img: Image.Image, bg_color: str, threshold: float) -> Image.Image: + """將 img 中 RGB 距離 bg_color 在 threshold 內的像素設為透明。""" + img = img.convert("RGBA") + pixels = np.array(img, dtype=np.int32) + key = np.array(_parse_hex(bg_color), dtype=np.int32) + dist = np.sqrt(np.sum((pixels[..., :3] - key) ** 2, axis=-1)) + out = pixels.copy() + out[..., 3] = np.where(dist <= threshold, 0, pixels[..., 3]) + return Image.fromarray(out.astype(np.uint8), "RGBA") + + +# ────────────────────────────────────────────── +# HTML 前端 +# ────────────────────────────────────────────── +HTML = """ + + + + Inset Crop Tool + + + + +
+ +

Inset Crop Tool

+ + +
+
+ + +
+ + +
+ +
+ + +
+

去底色設定

+
+ + +
+ Threshold: 100 + + +
+
+
+ +
+ + +
+

裁切設定

+
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+ + + +
+ + +
+ +
+

原始圖片

+
+ +

尚未上傳圖片

+
+
+ +
+
+

去底色預覽

+ +
+
+ +

預覽將在此顯示

+
+
+ +
+ + +
+ + +
+ +
+ + + + +""" + + +# ────────────────────────────────────────────── +# Routes +# ────────────────────────────────────────────── +@app.get("/", response_class=HTMLResponse) +async def index(): + return HTML + + +@app.post("/preview") +async def preview_bg( + file: UploadFile = File(...), + bg_color: str = Form("#ff00ff"), + threshold: float = Form(100.0), +): + """去底色後回傳 PNG,供前端即時預覽。""" + raw = await file.read() + try: + src = Image.open(io.BytesIO(raw)) + result = remove_background(src, bg_color, threshold) + except ValueError as e: + return PlainTextResponse(str(e), status_code=400) + buf = io.BytesIO() + result.save(buf, format="PNG") + buf.seek(0) + return StreamingResponse(buf, media_type="image/png") + + +@app.post("/process") +async def process( + file: UploadFile = File(...), + bg_color: str = Form("#ff00ff"), + threshold: float = Form(100.0), + cols: int = Form(...), + rows: int = Form(...), + inset: int = Form(...), +): + """去底色 → 內縮裁切 → 打包 ZIP 下載。""" + raw = await file.read() + try: + src = Image.open(io.BytesIO(raw)) + src = remove_background(src, bg_color, threshold) + except ValueError as e: + return PlainTextResponse(str(e), status_code=400) + + W, H = src.size + tile_w = W // cols + tile_h = H // rows + + if inset * 2 >= tile_w or inset * 2 >= tile_h: + return PlainTextResponse("內縮距離過大,超出格子尺寸", status_code=400) + + tiles: list[Image.Image] = [] + for r in range(rows): + for c in range(cols): + x0, y0 = c * tile_w, r * tile_h + cropped = src.crop((x0 + inset, y0 + inset, x0 + tile_w - inset, y0 + tile_h - inset)) + resized = cropped.resize((tile_w, tile_h), Image.NEAREST) + tiles.append(resized) + + combined = Image.new("RGBA", (cols * tile_w, rows * tile_h), (0, 0, 0, 0)) + for idx, tile in enumerate(tiles): + r, c = divmod(idx, cols) + combined.paste(tile, (c * tile_w, r * tile_h)) + + buf = io.BytesIO() + with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf: + for idx, tile in enumerate(tiles): + r, c = divmod(idx, cols) + tb = io.BytesIO() + tile.save(tb, format="PNG") + zf.writestr(f"tiles/tile_r{r:02d}_c{c:02d}.png", tb.getvalue()) + cb = io.BytesIO() + combined.save(cb, format="PNG") + zf.writestr("combined.png", cb.getvalue()) + + buf.seek(0) + return StreamingResponse( + buf, + media_type="application/zip", + headers={"Content-Disposition": "attachment; filename=inset_crop_output.zip"}, + ) + + +# ────────────────────────────────────────────── +if __name__ == "__main__": + import uvicorn + uvicorn.run("inset_crop_tool:app", host="0.0.0.0", port=8003, reload=True) diff --git a/tool.py b/tool.py index c3407c1..8a4ad4e 100644 --- a/tool.py +++ b/tool.py @@ -10,6 +10,7 @@ from sprite_webtool import app as picker_app from shiny_maker import app as shiny_app from rotate_webtool import app as flipper_app from sprite_merger import app as merger_app +from inset_crop_tool import app as inset_app app = FastAPI(title="Game Dev Suite") @@ -23,6 +24,7 @@ NAVBAR_HTML = """ ✨ Shiny Maker 🔄 Flipper 🧩 Merger + ✂️ Inset Crop """ @@ -58,6 +60,7 @@ app.mount("/picker", picker_app) app.mount("/shiny", shiny_app) app.mount("/flipper", flipper_app) app.mount("/merger", merger_app) +app.mount("/inset", inset_app) # 首頁入口 @app.get("/", response_class=HTMLResponse) @@ -80,6 +83,7 @@ async def index(): ✨ Shiny Maker 🔄 Flipper 🧩 Merger + ✂️ Inset Crop