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;