feat: enhance UI consistency, unified navigation middleware, and visual inset guides in Crop Tool (v1.2.0)

This commit is contained in:
2026-05-14 02:03:43 +08:00
parent ba763dc804
commit 981fae1693
9 changed files with 169 additions and 62 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -61,10 +61,13 @@ HTML = """<!DOCTYPE html>
}
</style>
</head>
<body class="bg-slate-900 text-white min-h-screen p-6">
<div class="max-w-7xl mx-auto space-y-4">
<body class="bg-slate-900 text-white">
<div class="max-w-7xl mx-auto space-y-6 p-6 pt-20">
<h1 class="text-2xl font-bold border-b border-slate-600 pb-3">Inset Crop Tool</h1>
<header class="border-b border-slate-700 pb-2">
<h1 class="text-xl font-bold">✂️ Inset Crop</h1>
<p class="text-slate-400 text-sm mt-2">移除背景色並內縮邊界,自動切割 Sprite Sheet 為個別方塊</p>
</header>
<!-- ── Top: Controls ── -->
<div class="bg-slate-800 rounded-2xl p-5">
@@ -100,25 +103,15 @@ HTML = """<!DOCTYPE html>
<div class="w-px self-stretch bg-slate-600"></div>
<!-- 裁切 -->
<!-- 預覽底色 -->
<div class="space-y-1">
<p class="text-xs font-semibold text-blue-300">裁切設定</p>
<p class="text-xs font-semibold text-cyan-300">預覽底色(檢查用)</p>
<div class="flex gap-2 items-center">
<div>
<label class="block text-xs text-slate-400 mb-1">Columns</label>
<input id="colsInput" type="number" value="4" min="1"
class="w-20 p-2 rounded-lg bg-slate-700 border border-slate-600 text-white text-sm">
</div>
<div>
<label class="block text-xs text-slate-400 mb-1">Rows</label>
<input id="rowsInput" type="number" value="6" min="1"
class="w-20 p-2 rounded-lg bg-slate-700 border border-slate-600 text-white text-sm">
</div>
<div>
<label class="block text-xs text-slate-400 mb-1">內縮 (px)</label>
<input id="insetInput" type="number" value="2" min="0"
class="w-20 p-2 rounded-lg bg-slate-700 border border-slate-600 text-white text-sm">
</div>
<input id="previewColorPicker" type="color" value="#333333"
class="w-10 h-9 rounded cursor-pointer border-0 p-0 bg-transparent">
<input id="previewColorHex" type="text" value="#333333" maxlength="7"
class="w-24 p-2 rounded-lg bg-slate-700 border border-slate-600 text-white font-mono text-sm">
<span class="text-xs text-slate-400">僅用於檢查,不影響匯出</span>
</div>
</div>
@@ -141,26 +134,53 @@ HTML = """<!DOCTYPE html>
<div class="bg-slate-800 rounded-2xl p-4 space-y-2">
<div class="flex items-center justify-between">
<p class="text-sm font-semibold text-pink-300">去底色預覽</p>
<p class="text-sm font-semibold text-cyan-300">去底色預覽(底色檢查用 + 內縮虛線)</p>
<span id="previewLoading" class="hidden text-xs text-slate-400 animate-pulse">更新中…</span>
</div>
<div class="checker flex items-center justify-center rounded-lg min-h-80 overflow-auto">
<div id="previewBg" class="relative flex items-center justify-center rounded-lg min-h-80 overflow-auto" style="background-color: #333333;">
<img id="bgImg" class="hidden max-w-full object-contain" style="image-rendering:pixelated">
<canvas id="insetCanvas" class="hidden absolute top-0 left-0" style="image-rendering:pixelated;"></canvas>
<p id="bgPH" class="text-slate-500 text-sm">預覽將在此顯示</p>
</div>
</div>
</div>
<!-- ── Bottom: Download ── -->
<div class="bg-slate-800 rounded-2xl p-4 flex items-center gap-4">
<button id="downloadBtn" disabled onclick="doProcess()"
class="px-10 py-3 bg-blue-600 hover:bg-blue-500
disabled:bg-slate-600 disabled:cursor-not-allowed
rounded-xl font-bold transition text-white">
⬇ 確認並下載 ZIP
</button>
<div id="status" class="hidden text-sm text-slate-400"></div>
<!-- ── Bottom: Crop Settings & Download ── -->
<div class="bg-slate-800 rounded-2xl p-4 flex items-center justify-between gap-6">
<!-- 左邊:裁切設定 -->
<div class="space-y-1">
<p class="text-xs font-semibold text-blue-300">裁切設定</p>
<div class="flex gap-2 items-center">
<div>
<label class="block text-xs text-slate-400 mb-1">Columns</label>
<input id="colsInput" type="number" value="4" min="1"
class="w-20 p-2 rounded-lg bg-slate-700 border border-slate-600 text-white text-sm">
</div>
<div>
<label class="block text-xs text-slate-400 mb-1">Rows</label>
<input id="rowsInput" type="number" value="6" min="1"
class="w-20 p-2 rounded-lg bg-slate-700 border border-slate-600 text-white text-sm">
</div>
<div>
<label class="block text-xs text-slate-400 mb-1">內縮 (px)</label>
<input id="insetInput" type="number" value="2" min="0"
class="w-20 p-2 rounded-lg bg-slate-700 border border-slate-600 text-white text-sm">
</div>
</div>
</div>
<!-- 右邊:下載按鈕 -->
<div class="flex flex-col items-center gap-2">
<button id="downloadBtn" disabled onclick="doProcess()"
class="px-10 py-3 bg-blue-600 hover:bg-blue-500
disabled:bg-slate-600 disabled:cursor-not-allowed
rounded-xl font-bold transition text-white whitespace-nowrap">
⬇ 確認並下載 ZIP
</button>
<div id="status" class="hidden text-sm text-slate-400"></div>
</div>
</div>
</div>
@@ -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 {

View File

@@ -19,13 +19,14 @@ HTML = '''<!DOCTYPE html>
::-webkit-scrollbar-thumb { background: #475569; border-radius: 4px; }
</style>
</head>
<body class="bg-slate-900 text-white p-6">
<div class="max-w-4xl mx-auto space-y-6">
<body class="bg-slate-900 text-white">
<div class="max-w-7xl mx-auto space-y-6 p-6 pt-20">
<header class="border-b border-slate-700 pb-2">
<h1 class="text-xl font-bold">🔄 Flipper</h1>
<p class="text-slate-400 text-sm mt-2">將 Sprite Sheet 中每個格子水平翻轉(左右鏡像),並輸出新圖片</p>
</header>
<div class="bg-slate-800 p-6 rounded-2xl border border-slate-700">
<h1 class="text-2xl font-bold text-blue-400 mb-1">🔄 Sprite Flipper</h1>
<p class="text-slate-400 text-sm mb-6">將 Sprite Sheet 中每個格子水平翻轉(左右鏡像),並輸出新圖片。</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<div>
<label class="block text-xs text-slate-400 mb-1">Columns列數</label>

View File

@@ -24,11 +24,11 @@ HTML = '''<!DOCTYPE html>
::-webkit-scrollbar-thumb { background: #475569; border-radius: 4px; }
</style>
</head>
<body class="bg-slate-900 text-white p-4">
<div class="max-w-4xl mx-auto space-y-6">
<header class="border-b border-slate-700 pb-4">
<h1 class="text-3xl font-bold text-blue-400">Shiny Monster Maker Pro</h1>
<p class="text-slate-400">全維度色違調整工具 (HSV 模式)</p>
<body class="bg-slate-900 text-white">
<div class="max-w-7xl mx-auto space-y-6 p-4 pt-20">
<header class="border-b border-slate-700 pb-2">
<h1 class="text-xl font-bold">Shiny Maker</h1>
<p class="text-slate-400 text-sm mt-2">全維度色違調整工具,支援 HSV 色相、飽和度、亮度變化</p>
</header>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">

View File

@@ -14,10 +14,12 @@ HTML = """
<title>Sprite Merger</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-slate-900 text-white min-h-screen p-8">
<div class="max-w-xl mx-auto space-y-6">
<h1 class="text-2xl font-bold text-blue-400">🧩 Sprite Merger</h1>
<p class="text-slate-400 text-sm">將來源圖片 merge 進主圖的指定 tile 座標</p>
<body class="bg-slate-900 text-white">
<div class="max-w-7xl mx-auto space-y-6 p-8 pt-20">
<header class="border-b border-slate-700 pb-2">
<h1 class="text-xl font-bold">🧩 Merger</h1>
<p class="text-slate-400 text-sm mt-2">將來源圖片 merge 進主圖的指定 tile 座標</p>
</header>
<form id="mergeForm" class="space-y-4">
<div>

View File

@@ -21,10 +21,15 @@ HTML = '''<!DOCTYPE html>
::-webkit-scrollbar-thumb { background: #475569; border-radius: 4px; }
</style>
</head>
<body class="bg-slate-900 text-white p-4">
<div class="max-w-7xl mx-auto grid grid-cols-1 lg:grid-cols-3 gap-4">
<body class="bg-slate-900 text-white">
<div class="max-w-7xl mx-auto p-4 pt-20 space-y-6">
<header class="border-b border-slate-700 pb-2">
<h1 class="text-xl font-bold">📏 Grid Tool</h1>
<p class="text-slate-400 text-sm mt-2">定義網格區域,批量選取多個 Tile 範圍</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
<div class="bg-slate-800 p-4 rounded-2xl space-y-2">
<h1 class="text-xl font-bold">Sprite Tool Suite</h1>
<div class="bg-blue-900/50 p-3 rounded-lg border border-blue-700/50">
<p class="text-sm font-semibold text-blue-200">Total Tiles: <span id="totalCount" class="text-xl text-white">0</span></p>
</div>

View File

@@ -22,11 +22,15 @@ HTML = '''<!DOCTYPE html>
::-webkit-scrollbar-thumb { background: #475569; border-radius: 4px; }
</style>
</head>
<body class="bg-slate-900 text-white p-4">
<div class="max-w-7xl mx-auto grid grid-cols-1 lg:grid-cols-4 gap-4">
<div class="lg:col-span-1 bg-slate-800 p-4 rounded-2xl space-y-4">
<h1 class="text-xl font-bold border-b border-slate-700 pb-2">Sprite Picker</h1>
<body class="bg-slate-900 text-white">
<div class="max-w-7xl mx-auto p-4 pt-20 space-y-6">
<header class="border-b border-slate-700 pb-2">
<h1 class="text-xl font-bold">🎯 Picker Tool</h1>
<p class="text-slate-400 text-sm mt-2">選取並導出單一 Sprite支援調整尺寸與排列</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-4 gap-4">
<div class="lg:col-span-1 bg-slate-800 p-4 rounded-2xl space-y-4">
<div class="bg-blue-900/30 p-3 rounded-lg border border-blue-700/50">
<p class="text-xs text-blue-200 mb-1">Total Selected</p>
<span id="totalCount" class="text-2xl font-bold">0</span>

View File

@@ -32,11 +32,7 @@ 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():
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-slate-900 text-white">
{NAVBAR_HTML}
<div class="flex flex-col items-center justify-center min-h-[80vh] space-y-6">
<div class="flex flex-col items-center justify-center min-h-[80vh] space-y-6 pt-20">
<h1 class="text-4xl font-bold text-blue-400">Game Developer Tool Suite</h1>
<p class="text-slate-400">請從上方導覽列選擇要使用的工具</p>
<div class="grid grid-cols-4 gap-6 pt-10">