← back to Dw Photo Capture
Calibrate-once measurement: card sizes px/in for the device once → stored in localStorage → every later scan just needs the goods box, no card. Zoom locked so focal length stays constant. Recalibrate button to re-anchor.
a2500f825754a1c70e09db378952ed6240876125 · 2026-07-06 21:43:31 -0700 · Steve Abrams
Files touched
Diff
commit a2500f825754a1c70e09db378952ed6240876125
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Jul 6 21:43:31 2026 -0700
Calibrate-once measurement: card sizes px/in for the device once → stored in localStorage → every later scan just needs the goods box, no card. Zoom locked so focal length stays constant. Recalibrate button to re-anchor.
---
public/index.html | 39 ++++++++++++++++++++++++++++++++-------
1 file changed, 32 insertions(+), 7 deletions(-)
diff --git a/public/index.html b/public/index.html
index d6f3dfb..e25f8d0 100644
--- a/public/index.html
+++ b/public/index.html
@@ -446,7 +446,12 @@
<div class="mbox mcard" id="mCard"><span class="mlbl">💳 card</span><i class="mrz"></i></div>
<div class="mbox mgoods" id="mSample"><span class="mlbl">📦 goods</span><i class="mrz"></i></div>
<div class="mreadout" id="mReadout">Size 💳 to a credit card, 📦 to the goods</div>
- <div class="mbar"><button id="mUse">✓ Use W×L</button><button id="mClose">✕ Close</button></div>
+ <div class="mbar">
+ <button id="mSaveCal" hidden>✓ Save calibration</button>
+ <button id="mUse" hidden>✓ Use W×L</button>
+ <button id="mRecal" hidden>↺ Recalibrate</button>
+ <button id="mClose">✕ Close</button>
+ </div>
</div>
<script>
const $=s=>document.querySelector(s), grid=$('#grid');
@@ -1612,10 +1617,23 @@ $('#addVoice').addEventListener('click',addPromptMissing); // manual re-trigge
// ── Live dimension measure: a credit card (3.375" wide) in frame calibrates pixels→inches ──
const CARD_IN=3.375; // ISO/IEC 7810 ID-1 (credit card) width
-let _mStream=null,_measW=0,_measL=0;
-function mRecompute(){ const c=$('#mCard'),s=$('#mSample'); const ppi=c.offsetWidth/CARD_IN;
- _measW=s.offsetWidth/ppi; _measL=s.offsetHeight/ppi;
- $('#mReadout').innerHTML=`W <b>${_measW.toFixed(1)}"</b> · L <b>${_measL.toFixed(1)}"</b> <small>· card=3.375"</small>`; }
+let _mStream=null,_measW=0,_measL=0,_ppi=0,_mMode='measure';
+// CALIBRATE ONCE: a card sizes px→inches for THIS device; stored in localStorage, reused forever.
+function calGet(){ try{ return JSON.parse(localStorage.getItem('dwMeasureCal')||'null'); }catch(e){ return null; } }
+function calSet(o){ try{ localStorage.setItem('dwMeasureCal',JSON.stringify(o)); }catch(e){} }
+function mSetMode(mode){ _mMode=mode;
+ $('#mCard').hidden = mode!=='calibrate'; // card only shown while calibrating
+ $('#mSample').hidden = mode==='calibrate';
+ $('#mSaveCal').hidden = mode!=='calibrate';
+ $('#mUse').hidden = mode==='calibrate';
+ $('#mRecal').hidden = mode==='calibrate';
+ mRecompute(); }
+function mRecompute(){
+ if(_mMode==='calibrate'){ _ppi=$('#mCard').offsetWidth/CARD_IN;
+ $('#mReadout').innerHTML=`One-time setup: size 💳 to a real credit card, then Save. <small>(${_ppi.toFixed(0)} px/in)</small>`; return; }
+ const s=$('#mSample'); if(!_ppi){ $('#mReadout').textContent='Tap ↺ Recalibrate first.'; return; }
+ _measW=s.offsetWidth/_ppi; _measL=s.offsetHeight/_ppi;
+ $('#mReadout').innerHTML=`W <b>${_measW.toFixed(1)}"</b> · L <b>${_measL.toFixed(1)}"</b> <small>· saved calibration · hold at your usual distance</small>`; }
function mMakeBox(el){ let mode=null,sx,sy,ox,oy,ow,oh; const rz=el.querySelector('.mrz');
const start=(e,m)=>{ mode=m; sx=e.clientX; sy=e.clientY; ox=el.offsetLeft; oy=el.offsetTop; ow=el.offsetWidth; oh=el.offsetHeight; (m==='resize'?rz:el).setPointerCapture(e.pointerId); e.preventDefault(); e.stopPropagation(); };
const move=e=>{ if(!mode)return; const dx=e.clientX-sx,dy=e.clientY-sy;
@@ -1635,12 +1653,19 @@ async function openMeasure(){
try{ const tr=_mStream.getVideoTracks()[0], caps=tr.getCapabilities?tr.getCapabilities():{};
if(caps.zoom) await tr.applyConstraints({advanced:[{zoom:caps.zoom.min||1}]}); }catch(e){}
$('#mVideo').srcObject=_mStream; $('#measureModal').hidden=false; document.body.style.overflow='hidden';
- const C=$('#mCard'),S=$('#mSample'); Object.assign(C.style,{left:'26px',top:'96px',width:'135px',height:'85px'});
- Object.assign(S.style,{left:'56px',top:'220px',width:'210px',height:'270px'}); mRecompute(); }
+ Object.assign($('#mCard').style,{left:'26px',top:'96px',width:'135px',height:'85px'});
+ Object.assign($('#mSample').style,{left:'56px',top:'200px',width:'210px',height:'270px'});
+ const cal=calGet();
+ if(cal&&cal.ppi){ _ppi=cal.ppi; mSetMode('measure'); } // reuse saved calibration — no card
+ else mSetMode('calibrate'); } // first time on this device → calibrate
function closeMeasure(){ if(_mStream){ _mStream.getTracks().forEach(t=>t.stop()); _mStream=null; } $('#measureModal').hidden=true; document.body.style.overflow=''; }
mMakeBox($('#mCard')); mMakeBox($('#mSample'));
$('#addMeasure').addEventListener('click',openMeasure);
$('#mClose').addEventListener('click',closeMeasure);
+$('#mRecal').addEventListener('click',()=>mSetMode('calibrate'));
+$('#mSaveCal').addEventListener('click',()=>{ mRecompute();
+ if(_ppi>0){ calSet({ppi:_ppi, sw:window.innerWidth, ts:Date.now()});
+ toast('✓ Calibrated ('+_ppi.toFixed(0)+' px/in) — no card needed next time'); mSetMode('measure'); } });
$('#mUse').addEventListener('click',()=>{ const w=_measW.toFixed(1)+'"',l=_measL.toFixed(1)+'"';
_addExtracted.width=w; _addExtracted.roll_length=l;
$('#addSpecs').insertAdjacentHTML('afterbegin',`<div class="add-spec"><span>📐 Measured</span><b>${w} × ${l}</b></div>`);
← 8cff6c4 Camera-unavailable UX: detect Chrome/3rd-party iOS browsers
·
back to Dw Photo Capture
·
yolo hardening (8 fixes from adversarial review): C1 visual 1570feb →