[object Object]

← 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 →