← back to Dw Photo Capture
iOS photo-processing freeze fix: downscale() now uses createImageBitmap (off-main-thread, low-memory decode) instead of <img>+canvas.toDataURL which OOM-hangs iOS on a 12MP camera photo; cap 1200px + q0.82. fbRead shows '📷 Processing photo…' immediately + guards a null (undecodable) read
d5ff7da615fd23cb0a78bd113ee3a39ee89ad705 · 2026-07-08 15:43:57 -0700 · Steve Abrams
Files touched
Diff
commit d5ff7da615fd23cb0a78bd113ee3a39ee89ad705
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Jul 8 15:43:57 2026 -0700
iOS photo-processing freeze fix: downscale() now uses createImageBitmap (off-main-thread, low-memory decode) instead of <img>+canvas.toDataURL which OOM-hangs iOS on a 12MP camera photo; cap 1200px + q0.82. fbRead shows '📷 Processing photo…' immediately + guards a null (undecodable) read
---
public/index.html | 42 +++++++++++++++++++++++++++++-------------
1 file changed, 29 insertions(+), 13 deletions(-)
diff --git a/public/index.html b/public/index.html
index dc85f79..7333262 100644
--- a/public/index.html
+++ b/public/index.html
@@ -831,18 +831,32 @@ function lazyThumbs(){
_thumbObs=new IntersectionObserver((es,o)=>{ for(const e of es){ if(e.isIntersecting){ const t=e.target,u=t.getAttribute('data-bg'); if(u){ t.style.backgroundImage=`url('${u}')`; t.removeAttribute('data-bg'); } o.unobserve(t); } } },{rootMargin:'400px'});
grid.querySelectorAll('.thumb[data-bg]').forEach(t=>_thumbObs.observe(t));
}
-function downscale(file,max=2000){return new Promise((res)=>{
- const img=new Image(), url=URL.createObjectURL(file);
- let done=false; const finish=v=>{ if(done)return; done=true; try{URL.revokeObjectURL(url);}catch(e){} res(v); };
- img.onload=()=>{
- let{width:w,height:h}=img; const s=Math.min(1,max/Math.max(w,h)); // never upscale
- const c=document.createElement('canvas'); c.width=Math.round(w*s); c.height=Math.round(h*s);
- c.getContext('2d').drawImage(img,0,0,c.width,c.height); finish(c.toDataURL('image/jpeg',0.9));
- };
- img.onerror=()=>finish(null); // undecodable (HEIC/corrupt) → null, never hang
- setTimeout(()=>finish(null),15000); // hard timeout safety net
- img.src=url;
-});}
+// iOS FREEZE FIX: a full 12MP camera photo decoded via <img>+canvas.toDataURL is a memory hog that
+// hangs/OOM-crashes iOS Safari. createImageBitmap decodes OFF the main thread + low-memory, so try it
+// first; fall back to <img> only if unavailable. Also cap smaller (OCR needs ~1200px, not 2000).
+async function downscale(file,max=1200){
+ try{
+ if('createImageBitmap' in window){
+ const bmp=await createImageBitmap(file);
+ const s=Math.min(1,max/Math.max(bmp.width,bmp.height));
+ const c=document.createElement('canvas'); c.width=Math.max(1,Math.round(bmp.width*s)); c.height=Math.max(1,Math.round(bmp.height*s));
+ c.getContext('2d').drawImage(bmp,0,0,c.width,c.height); if(bmp.close)bmp.close();
+ return c.toDataURL('image/jpeg',0.82);
+ }
+ }catch(e){ /* fall through to <img> path */ }
+ return new Promise((res)=>{
+ const img=new Image(), url=URL.createObjectURL(file);
+ let done=false; const finish=v=>{ if(done)return; done=true; try{URL.revokeObjectURL(url);}catch(e){} res(v); };
+ img.onload=()=>{
+ let{width:w,height:h}=img; const s=Math.min(1,max/Math.max(w,h));
+ const c=document.createElement('canvas'); c.width=Math.round(w*s); c.height=Math.round(h*s);
+ c.getContext('2d').drawImage(img,0,0,c.width,c.height); finish(c.toDataURL('image/jpeg',0.82));
+ };
+ img.onerror=()=>finish(null);
+ setTimeout(()=>finish(null),12000);
+ img.src=url;
+ });
+}
// Capture → open the editor (review + enhance) instead of uploading immediately.
async function shoot(x,file){
if(!file)return;
@@ -1519,7 +1533,9 @@ function loadJsQR(){ if(_jsqr) return Promise.resolve(_jsqr); return new Promise
async function decodeQR(dataUrl){ const jsQR=await loadJsQR(); if(!jsQR) return null;
return new Promise(r=>{ const im=new Image(); im.onload=()=>{ try{ const c=document.createElement('canvas'); c.width=im.naturalWidth; c.height=im.naturalHeight; const x=c.getContext('2d'); x.drawImage(im,0,0); const d=x.getImageData(0,0,c.width,c.height); const q=jsQR(d.data,c.width,c.height); r(q?q.data:null);}catch(e){r(null);} }; im.onerror=()=>r(null); im.src=dataUrl; }); }
async function fbRead(file,isBack){ if(!file)return;
- const url=await downscale(file,1400);
+ try{ $('#fbNote').textContent='📷 Processing photo…'; }catch(e){}
+ const url=await downscale(file,1200);
+ if(!url){ try{ $('#fbNote').textContent='Could not read that photo — try again.'; }catch(e){} return; }
if(isBack){ _fbBack=url; const t=$('#fbBackThumb'); t.style.backgroundImage=`url('${url}')`; t.textContent=''; $('#fbBackTile').classList.add('set');
$('#fbNote').textContent='Reading label + QR…'; _fbQR=await decodeQR(url).catch(()=>null); $('#fbNote').textContent=_fbQR?'🔗 QR code read':''; }
else { _fbFront=url; const t=$('#fbFrontThumb'); t.style.backgroundImage=`url('${url}')`; t.textContent=''; $('#fbFrontTile').classList.add('set'); }
← ef6656d Fix iOS camera freeze on Look Up SKU: stop auto-clicking the
·
back to Dw Photo Capture
·
iOS freeze — eliminate ALL client-side image decode (DTD ver 71b8574 →