[object Object]

← back to Dw Photo Capture

Fix front-camera scan: mirror-retry (ocrSmart) recovers backwards-text reads on iOS selfie cam; wired into live + snap scan

4ba1fb1e7038d1adb5b590eb1a9ff3b0030a346e · 2026-06-25 10:43:40 -0700 · Steve

Files touched

Diff

commit 4ba1fb1e7038d1adb5b590eb1a9ff3b0030a346e
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Jun 25 10:43:40 2026 -0700

    Fix front-camera scan: mirror-retry (ocrSmart) recovers backwards-text reads on iOS selfie cam; wired into live + snap scan
---
 data/build.json   |  5 +++--
 public/index.html | 16 ++++++++++++----
 2 files changed, 15 insertions(+), 6 deletions(-)

diff --git a/data/build.json b/data/build.json
index c966201..e03b323 100644
--- a/data/build.json
+++ b/data/build.json
@@ -1,5 +1,5 @@
 {
-  "next": 39,
+  "next": 40,
   "map": {
     "63863152": 27,
     "578af86f": 2,
@@ -37,6 +37,7 @@
     "008cadb1": 35,
     "237a0e5e": 36,
     "57975a47": 37,
-    "331a91f4": 38
+    "331a91f4": 38,
+    "dee91484": 39
   }
 }
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
index e98046c..41cbe6b 100644
--- a/public/index.html
+++ b/public/index.html
@@ -858,6 +858,16 @@ async function flipCam(){   // toggle back<->front, persist, re-acquire live + r
   else toast(camFacing==='user'?'📷 Front camera':'📷 Back camera');
 }
 function closeLiveScan(){ if(scanTimer)clearInterval(scanTimer); scanTimer=null; if(scanStream){scanStream.getTracks().forEach(t=>t.stop());scanStream=null;} $('#scanlive').hidden=true; document.body.style.overflow=''; }
+async function ocrPost(dataUrl){ const r=await fetch('/api/ocr',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({dataUrl})}); return r.json(); }
+function mirrorDataUrl(dataUrl){ return new Promise((res,rej)=>{ const im=new Image(); im.onload=()=>{ const c=document.createElement('canvas'); c.width=im.naturalWidth; c.height=im.naturalHeight; const x=c.getContext('2d'); x.translate(c.width,0); x.scale(-1,1); x.drawImage(im,0,0); res(c.toDataURL('image/jpeg',0.8)); }; im.onerror=rej; im.src=dataUrl; }); }
+// front camera can deliver a MIRRORED frame (iOS) → backwards text reads as garbage. If a front-cam read finds nothing, retry flipped.
+async function ocrSmart(dataUrl){
+  let d=await ocrPost(dataUrl);
+  if(camFacing==='user' && (!d||!d.candidates||!d.candidates.length)){
+    try{ const d2=await ocrPost(await mirrorDataUrl(dataUrl)); if(d2&&d2.candidates&&d2.candidates.length) return d2; }catch(e){}
+  }
+  return d;
+}
 async function scanTick(){
   if(scanBusy)return; scanBusy=true;
   try{
@@ -865,8 +875,7 @@ async function scanTick(){
     const s=Math.min(1,1280/Math.max(v.videoWidth,v.videoHeight));
     const c=document.createElement('canvas'); c.width=Math.round(v.videoWidth*s); c.height=Math.round(v.videoHeight*s);
     c.getContext('2d').drawImage(v,0,0,c.width,c.height);
-    const r=await fetch('/api/ocr',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({dataUrl:c.toDataURL('image/jpeg',0.7)})});
-    const d=await r.json();
+    const d=await ocrSmart(c.toDataURL('image/jpeg',0.7));   // front-cam mirror-retry built in
     if(d&&d.top){
       if(d.top===_lastTop) _lockHits++; else {_lastTop=d.top;_lockHits=1;}
       $('#scanStatus').textContent=(_lockHits>=2?'🔒 LOCKED · ':'👀 ')+d.top;
@@ -922,8 +931,7 @@ $('#scanInput').addEventListener('change',async e=>{
   toast('📷 Reading number…');
   const dataUrl=await downscale(f,1600);
   try{
-    const r=await fetch('/api/ocr',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({dataUrl})});
-    const d=await r.json();
+    const d=await ocrSmart(dataUrl);   // front-cam mirror-retry + multi-candidate
     e.target.value='';
     // multi-pass resolve: every OCR candidate against TWIL sku/mfr/name → fall through to all-Shopify
     await applyScan(d);

← 3355c70 Camera chooser: back/front toggle (header + live-scan flip),  ·  back to Dw Photo Capture  ·  Add dev-only ESLint static gate + remove dead tcase helper 9a72bcb →