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;