From 981fae16933a229e821bdbbd8b129864b0d47c6e Mon Sep 17 00:00:00 2001 From: Edward Chang Date: Thu, 14 May 2026 02:03:43 +0800 Subject: [PATCH] feat: enhance UI consistency, unified navigation middleware, and visual inset guides in Crop Tool (v1.2.0) --- README.md | 6 +- README_EN.md | 6 +- inset_crop_tool.py | 156 +++++++++++++++++++++++++++++++-------- rotate_webtool.py | 11 +-- shiny_maker.py | 10 +-- sprite_merger.py | 10 ++- sprite_tool_fullstack.py | 11 ++- sprite_webtool.py | 12 ++- tool.py | 9 +-- 9 files changed, 169 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index e78e2b2..59655d0 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,8 @@ SpriteTool 是一個整合式的 Web 應用程式,集合了四個專業級工 **功能特性:** - **色彩去除** - 自訂底色(預設 #ff00ff 粉紅色)及去除閾值 - **即時預覽** - 調整 threshold 時實時顯示去底色效果 +- **預覽底色檢查** - 可修改預覽背景顏色以檢查邊界精度(純檢查用,不影響導出) +- **內縮虛線框** - 在預覽上顯示每個格子的內縮邊界(粉紅色虛線框),視覺化檢查內縮效果 - **智慧裁切** - 根據行列數和內縮距離精確分割每格 - **縮放還原** - 內縮後自動放大回原格尺寸 - **批量輸出** - 導出個別格子 PNG + 組合完整圖 @@ -278,6 +280,8 @@ SpriteTool/ ## 🎨 UI/UX 特性 - **深色主題** - 護眼的深灰配色 +- **統一導覽體驗** - 透過 FastAPI 中間件在所有工具頁面自動注入導覽列 +- **標準化佈局** - 所有工具採用一致的寬螢幕佈局(Max-width 7xl)與標題樣式 - **響應式設計** - 適配桌面和平板 - **即時預覽** - 無延遲的視覺反饋 - **拖拽交互** - 直觀的圖像操作 @@ -333,4 +337,4 @@ SpriteTool/ --- **最後更新**: 2026 年 5 月 -**版本**: 1.1.0 +**版本**: 1.2.0 diff --git a/README_EN.md b/README_EN.md index efcd54b..f8fd7aa 100644 --- a/README_EN.md +++ b/README_EN.md @@ -89,6 +89,8 @@ Remove Chroma Key background and perform intelligent inset cropping and enlargem **Key Features:** - **Color Removal** - Custom chroma key (default #ff00ff pink) and threshold adjustment. - **Real-time Preview** - Instantly view background removal effects while adjusting threshold. +- **Preview Background Customization** - Change preview background color to check edge precision (inspection only, does not affect export). +- **Inset Guide Lines** - Display dashed lines showing each cell's inset boundary (pink dashed outline) for visual verification of inset effects. - **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. @@ -273,6 +275,8 @@ SpriteTool/ ## 🎨 UI/UX Features - **Dark Theme** - Eye-friendly dark gray color scheme. +- **Unified Navigation** - Navigation bar is automatically injected into all tool pages via FastAPI middleware. +- **Standardized Layout** - Consistent wide-screen layout (Max-width 7xl) and header styling across all tools. - **Responsive Design** - Optimized for desktop and tablets. - **Instant Preview** - Lag-free visual feedback. - **Drag-and-Drop Interaction** - Intuitive image manipulation. @@ -328,4 +332,4 @@ Issues and Pull Requests are welcome! --- **Last Updated**: May 2026 -**Version**: 1.1.0 +**Version**: 1.2.0 diff --git a/inset_crop_tool.py b/inset_crop_tool.py index 47af2c8..4dae0b4 100644 --- a/inset_crop_tool.py +++ b/inset_crop_tool.py @@ -61,10 +61,13 @@ HTML = """ } - -
+ +
-

Inset Crop Tool

+
+

✂️ Inset Crop

+

移除背景色並內縮邊界,自動切割 Sprite Sheet 為個別方塊

+
@@ -100,25 +103,15 @@ HTML = """
- +
-

裁切設定

+

預覽底色(檢查用)

-
- - -
-
- - -
-
- - -
+ + + 僅用於檢查,不影響匯出
@@ -141,26 +134,53 @@ HTML = """
-

去底色預覽

+

去底色預覽(底色檢查用 + 內縮虛線)

-
+
+

預覽將在此顯示

- -
- - + +
+ + +
+

裁切設定

+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+ + +
@@ -175,8 +195,12 @@ const origImg = document.getElementById('origImg'); const origPH = document.getElementById('origPH'); const bgImg = document.getElementById('bgImg'); const bgPH = document.getElementById('bgPH'); +const insetCanvas = document.getElementById('insetCanvas'); +const previewBg = document.getElementById('previewBg'); const colorPicker = document.getElementById('colorPicker'); const colorHex = document.getElementById('colorHex'); +const previewColorPicker = document.getElementById('previewColorPicker'); +const previewColorHex = document.getElementById('previewColorHex'); const threshRange = document.getElementById('threshRange'); const threshNum = document.getElementById('threshNum'); const threshLabel = document.getElementById('threshLabel'); @@ -185,6 +209,19 @@ const statusDiv = document.getElementById('status'); const downloadBtn = document.getElementById('downloadBtn'); const previewLoading = document.getElementById('previewLoading'); +// ── Preview color picker <-> hex ───────────── +previewColorPicker.addEventListener('input', () => { + previewColorHex.value = previewColorPicker.value; + previewBg.style.backgroundColor = previewColorPicker.value; +}); +previewColorHex.addEventListener('change', () => { + const v = previewColorHex.value.trim(); + if (/^#[0-9a-fA-F]{6}$/.test(v)) { + previewColorPicker.value = v; + previewBg.style.backgroundColor = v; + } +}); + // ── Color picker <-> hex ───────────────────── colorPicker.addEventListener('input', () => { colorHex.value = colorPicker.value; @@ -224,7 +261,59 @@ fileInput.addEventListener('change', () => { origImg.addEventListener('load', updateInfo); ['colsInput','rowsInput','insetInput'].forEach(id => - document.getElementById(id).addEventListener('input', updateInfo)); + document.getElementById(id).addEventListener('input', () => { + updateInfo(); + drawInsetGrid(); + })); + +// ── Draw inset grid on canvas ──────────────── +function drawInsetGrid() { + if (!bgImg.naturalWidth || bgImg.classList.contains('hidden')) return; + + const cols = +document.getElementById('colsInput').value || 1; + const rows = +document.getElementById('rowsInput').value || 1; + const inset = +document.getElementById('insetInput').value || 0; + + const imgW = bgImg.naturalWidth; + const imgH = bgImg.naturalHeight; + const tileW = Math.floor(imgW / cols); + const tileH = Math.floor(imgH / rows); + + // 設置Canvas的繪製解析度(邏輯像素) + insetCanvas.width = imgW; + insetCanvas.height = imgH; + + // 計算bgImg的實際顯示大小 + const displayW = bgImg.offsetWidth; + const displayH = bgImg.offsetHeight; + const offsetLeft = bgImg.offsetLeft; + const offsetTop = bgImg.offsetTop; + + // 設置Canvas的CSS顯示大小和位置以匹配bgImg + insetCanvas.style.width = displayW + 'px'; + insetCanvas.style.height = displayH + 'px'; + insetCanvas.style.left = offsetLeft + 'px'; + insetCanvas.style.top = offsetTop + 'px'; + + const ctx = insetCanvas.getContext('2d'); + ctx.clearRect(0, 0, imgW, imgH); + ctx.strokeStyle = 'rgba(255, 100, 200, 0.7)'; // 粉紅色虛線 + ctx.setLineDash([4, 4]); + ctx.lineWidth = 1.5; + + // 對每個格子繪製內縮邊界框 + for (let r = 0; r < rows; r++) { + for (let c = 0; c < cols; c++) { + const x0 = c * tileW; + const y0 = r * tileH; + + // 繪製內縮矩形框 + ctx.strokeRect(x0 + inset, y0 + inset, tileW - inset * 2, tileH - inset * 2); + } + } + + insetCanvas.classList.remove('hidden'); +} function updateInfo() { if (!currentFile || !origImg.naturalWidth) return; @@ -266,6 +355,9 @@ async function doPreview() { bgImg.src = previewObjectUrl; bgImg.classList.remove('hidden'); bgPH.classList.add('hidden'); + bgImg.onload = () => { + drawInsetGrid(); + }; } catch (e) { console.error(e); } finally { diff --git a/rotate_webtool.py b/rotate_webtool.py index 076d141..7ace537 100644 --- a/rotate_webtool.py +++ b/rotate_webtool.py @@ -19,13 +19,14 @@ HTML = ''' ::-webkit-scrollbar-thumb { background: #475569; border-radius: 4px; } - -
+ +
+
+

🔄 Flipper

+

將 Sprite Sheet 中每個格子水平翻轉(左右鏡像),並輸出新圖片

+
-

🔄 Sprite Flipper

-

將 Sprite Sheet 中每個格子水平翻轉(左右鏡像),並輸出新圖片。

-
diff --git a/shiny_maker.py b/shiny_maker.py index cae5eef..cd05491 100644 --- a/shiny_maker.py +++ b/shiny_maker.py @@ -24,11 +24,11 @@ HTML = ''' ::-webkit-scrollbar-thumb { background: #475569; border-radius: 4px; } - -
-
-

Shiny Monster Maker Pro

-

全維度色違調整工具 (HSV 模式)

+ +
+
+

✨ Shiny Maker

+

全維度色違調整工具,支援 HSV 色相、飽和度、亮度變化

diff --git a/sprite_merger.py b/sprite_merger.py index 629ce24..4c559fe 100644 --- a/sprite_merger.py +++ b/sprite_merger.py @@ -14,10 +14,12 @@ HTML = """ Sprite Merger - -
-

🧩 Sprite Merger

-

將來源圖片 merge 進主圖的指定 tile 座標

+ +
+
+

🧩 Merger

+

將來源圖片 merge 進主圖的指定 tile 座標

+
diff --git a/sprite_tool_fullstack.py b/sprite_tool_fullstack.py index c5a34eb..395e4cb 100644 --- a/sprite_tool_fullstack.py +++ b/sprite_tool_fullstack.py @@ -21,10 +21,15 @@ HTML = ''' ::-webkit-scrollbar-thumb { background: #475569; border-radius: 4px; } - -
+ +
+
+

📏 Grid Tool

+

定義網格區域,批量選取多個 Tile 範圍

+
+ +
-

Sprite Tool Suite

Total Tiles: 0

diff --git a/sprite_webtool.py b/sprite_webtool.py index c7c676d..3c82ded 100644 --- a/sprite_webtool.py +++ b/sprite_webtool.py @@ -22,11 +22,15 @@ HTML = ''' ::-webkit-scrollbar-thumb { background: #475569; border-radius: 4px; } - -
+ +
+
+

🎯 Picker Tool

+

選取並導出單一 Sprite,支援調整尺寸與排列

+
+ +
-

Sprite Picker

-

Total Selected

0 diff --git a/tool.py b/tool.py index 8a4ad4e..3acbc8f 100644 --- a/tool.py +++ b/tool.py @@ -31,12 +31,8 @@ NAVBAR_HTML = """ class NavbarMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): response = await call_next(request) - - # 排除首頁,避免重複注入 - if request.url.path == "/": - return response - # 針對所有子工具的 HTML 回傳進行注入 + # 針對所有 HTML 回傳進行注入 if "text/html" in response.headers.get("content-type", ""): body = b"" async for chunk in response.body_iterator: @@ -73,8 +69,7 @@ async def index(): - {NAVBAR_HTML} -
+

Game Developer Tool Suite

請從上方導覽列選擇要使用的工具