From 1c806d45e12f1bbd4715a25bed9c08c4cbf6c4e0 Mon Sep 17 00:00:00 2001 From: JianMiau Date: Tue, 28 Jul 2026 11:07:08 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E5=82=B3=E7=85=A7=E7=89=87=E6=99=82?= =?UTF-8?q?=E8=87=AA=E5=8B=95=E5=9C=A8=E7=80=8F=E8=A6=BD=E5=99=A8=E7=AB=AF?= =?UTF-8?q?=E7=B8=AE=E5=9C=96=E5=A3=93=E7=B8=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根本原因: 照片一律以原始解析度存檔(手機相機動輒 2000px 以上、數 MB),除了先前已修的編譯端問題,也拖慢上傳、佔用 NAS 空間與管理頁縮圖載入。 影響: 上傳大照片時傳輸慢、data 目錄膨脹;縮圖修正前上傳的原圖也一直以全尺寸存放。 修法: 管理頁上傳前先在瀏覽器把照片縮到長邊 1280px、以 JPEG 90% 重新編碼(canvas 會把 EXIF 方向烘進像素,直式照片不會轉向),訊息列顯示壓縮前後大小;MAX_EDGE 常數與編譯端共用,編譯端縮圖保留作為舊照片的雙保險。已在本機實測:2244x4124 / 1.4MB 原圖上傳後存為 696x1280 / 170KB。 Co-Authored-By: Claude Fable 5 --- public/admin.html | 51 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/public/admin.html b/public/admin.html index 17203ef..1aae1d5 100644 --- a/public/admin.html +++ b/public/admin.html @@ -59,7 +59,7 @@

新增配對

- +
@@ -146,15 +146,55 @@ async function refresh() { } } +// 照片統一縮到這個長邊尺寸:對 MindAR 特徵品質足夠, +// 且掃描端(尤其 iOS Safari)的 WebGL 記憶體撐得住 +const MAX_EDGE = 1280; + +function loadImageFromFile(file) { + return new Promise((resolve, reject) => { + const url = URL.createObjectURL(file); + const img = new Image(); + img.onload = () => { URL.revokeObjectURL(url); resolve(img); }; + img.onerror = () => { URL.revokeObjectURL(url); reject(new Error('照片檔案無法讀取')); }; + img.src = url; + }); +} + +// 上傳前先在瀏覽器縮圖壓縮(長邊 MAX_EDGE、JPEG 90%), +// 伺服器不需裝任何影像處理套件。canvas 重新編碼時 +// 瀏覽器會把 EXIF 方向烘進像素,直式照片不會轉向 +async function optimizePhoto(file) { + const img = await loadImageFromFile(file); + const scale = Math.min(1, MAX_EDGE / Math.max(img.naturalWidth, img.naturalHeight)); + if (scale === 1) return file; + const c = document.createElement('canvas'); + c.width = Math.round(img.naturalWidth * scale); + c.height = Math.round(img.naturalHeight * scale); + c.getContext('2d').drawImage(img, 0, 0, c.width, c.height); + const blob = await new Promise((r) => c.toBlob(r, 'image/jpeg', 0.9)); + if (!blob) throw new Error('照片壓縮失敗'); + return new File([blob], file.name.replace(/\.\w+$/, '') + '.jpg', { type: 'image/jpeg' }); +} + document.getElementById('addForm').addEventListener('submit', async (e) => { e.preventDefault(); const btn = document.getElementById('addBtn'); const msg = document.getElementById('msg'); btn.disabled = true; msg.className = ''; - msg.textContent = '上傳中…'; try { - const res = await fetch('/api/pairs', { method: 'POST', body: new FormData(e.target) }); + const rawPhoto = e.target.photo.files[0]; + msg.textContent = '照片壓縮中…'; + const photo = await optimizePhoto(rawPhoto); + const mb = (n) => (n / 1024 / 1024).toFixed(1) + 'MB'; + msg.textContent = photo === rawPhoto + ? '上傳中…' + : `上傳中…(照片 ${mb(rawPhoto.size)} → ${mb(photo.size)})`; + + const fd = new FormData(); + fd.append('photo', photo, photo.name); + fd.append('video', e.target.video.files[0]); + const res = await fetch('/api/pairs', { method: 'POST', body: fd }); const json = await res.json(); if (!res.ok) throw new Error(json.error || res.statusText); msg.textContent = '已新增,請記得重新編譯。'; @@ -211,11 +251,10 @@ document.getElementById('compileBtn').addEventListener('click', async () => { pairs.map((p) => loadImage('/data/photos/' + encodeURIComponent(p.photo))) ); - // 大圖先縮到長邊 1280px 再編譯:特徵品質足夠, - // 且 .mind 檔與掃描端(尤其 iOS Safari)的 WebGL 記憶體用量大幅下降。 + // 大圖先縮到長邊 MAX_EDGE 再編譯(雙保險:新上傳的照片已在上傳時縮圖, + // 這裡再保護縮圖修正前上傳的舊照片)。 // 曾發生 4 張 2244x4131 原圖直接編譯後,iPhone 掃任何照片都沒反應 // (桌面 Chrome 正常,iOS WebGL 資源上限較低,辨識引擎無聲掛掉) - const MAX_EDGE = 1280; const compileInputs = images.map((img) => { const scale = Math.min(1, MAX_EDGE / Math.max(img.naturalWidth, img.naturalHeight)); if (scale === 1) return img;