Camera selector, focus controls, and decode improvements
- Camera picker with auto-selection of main back camera (index 0) - Manual/auto focus toggle button with focus distance slider - Dual binarizer (Hybrid + GlobalHistogram) for blur tolerance - Only vibrate on new unique scan result - Band canvas to restrict decoding to aim line region - Camera select overlaid in viewfinder corner Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
23
app.mjs
23
app.mjs
@@ -11,7 +11,8 @@ const cam_info = document.getElementById('cam-info');
|
|||||||
const focus_row = document.getElementById('focus-row');
|
const focus_row = document.getElementById('focus-row');
|
||||||
const focus_slider = document.getElementById('focus-slider');
|
const focus_slider = document.getElementById('focus-slider');
|
||||||
const focus_val_el = document.getElementById('focus-val');
|
const focus_val_el = document.getElementById('focus-val');
|
||||||
const focus_auto = document.getElementById('focus-auto');
|
const focus_auto_btn = document.getElementById('focus-auto-btn');
|
||||||
|
let focus_auto = true;
|
||||||
// Canvases for ZXing decode pipeline
|
// Canvases for ZXing decode pipeline
|
||||||
const strip_canvas = document.createElement('canvas');
|
const strip_canvas = document.createElement('canvas');
|
||||||
const strip_ctx = strip_canvas.getContext('2d', { willReadFrequently: true });
|
const strip_ctx = strip_canvas.getContext('2d', { willReadFrequently: true });
|
||||||
@@ -76,11 +77,18 @@ async function start_camera(device_id = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!has_continuous) {
|
if (!has_continuous) {
|
||||||
focus_auto.checked = false;
|
focus_auto = false;
|
||||||
focus_auto.disabled = true;
|
focus_auto_btn.disabled = true;
|
||||||
|
} else {
|
||||||
|
focus_auto_btn.disabled = false;
|
||||||
}
|
}
|
||||||
|
focus_auto_btn.classList.toggle('active', focus_auto);
|
||||||
focus_auto.onchange = () => apply_focus(track, has_continuous, has_distance);
|
focus_auto_btn.onclick = async () => {
|
||||||
|
focus_auto = !focus_auto;
|
||||||
|
focus_auto_btn.classList.toggle('active', focus_auto);
|
||||||
|
focus_slider.disabled = focus_auto;
|
||||||
|
await apply_focus(active_track, has_continuous, has_distance);
|
||||||
|
};
|
||||||
await apply_focus(track, has_continuous, has_distance);
|
await apply_focus(track, has_continuous, has_distance);
|
||||||
|
|
||||||
// Tap to focus (replace listener on camera switch)
|
// Tap to focus (replace listener on camera switch)
|
||||||
@@ -98,8 +106,7 @@ async function start_camera(device_id = null) {
|
|||||||
// --- Focus ---
|
// --- Focus ---
|
||||||
|
|
||||||
async function apply_focus(track, has_continuous, has_distance) {
|
async function apply_focus(track, has_continuous, has_distance) {
|
||||||
const auto = focus_auto.checked;
|
const auto = focus_auto;
|
||||||
focus_slider.disabled = auto;
|
|
||||||
try {
|
try {
|
||||||
if (auto && has_continuous) {
|
if (auto && has_continuous) {
|
||||||
await track.applyConstraints({ advanced: [{ focusMode: 'continuous' }] });
|
await track.applyConstraints({ advanced: [{ focusMode: 'continuous' }] });
|
||||||
@@ -276,9 +283,7 @@ function attempt_decode() {
|
|||||||
band_canvas.width = strip_canvas.width;
|
band_canvas.width = strip_canvas.width;
|
||||||
band_canvas.height = band_h;
|
band_canvas.height = band_h;
|
||||||
}
|
}
|
||||||
band_ctx.filter = 'contrast(1.8) brightness(1.05)';
|
|
||||||
band_ctx.drawImage(strip_canvas, 0, band_y, strip_canvas.width, band_h, 0, 0, strip_canvas.width, band_h);
|
band_ctx.drawImage(strip_canvas, 0, band_y, strip_canvas.width, band_h, 0, 0, strip_canvas.width, band_h);
|
||||||
band_ctx.filter = 'none';
|
|
||||||
|
|
||||||
decode_attempts++;
|
decode_attempts++;
|
||||||
const source = new ZXing.HTMLCanvasElementLuminanceSource(band_canvas);
|
const source = new ZXing.HTMLCanvasElementLuminanceSource(band_canvas);
|
||||||
|
|||||||
39
index.html
39
index.html
@@ -55,6 +55,20 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#camera-select {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 8px;
|
||||||
|
right: 8px;
|
||||||
|
max-width: 60%;
|
||||||
|
padding: 4px 6px;
|
||||||
|
background: rgba(0,0,0,0.6);
|
||||||
|
color: #dce4ef;
|
||||||
|
border: 1px solid #1e2a38;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
#debug {
|
#debug {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 6px;
|
top: 6px;
|
||||||
@@ -117,6 +131,24 @@
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#focus-auto-btn {
|
||||||
|
padding: 8px 14px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #1e2a38;
|
||||||
|
background: #181e28;
|
||||||
|
color: var(--dim);
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
#focus-auto-btn.active {
|
||||||
|
background: #0d1f15;
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
#focus-slider {
|
#focus-slider {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
accent-color: var(--accent);
|
accent-color: var(--accent);
|
||||||
@@ -147,19 +179,16 @@
|
|||||||
<video id="video" autoplay playsinline muted></video>
|
<video id="video" autoplay playsinline muted></video>
|
||||||
<canvas id="overlay"></canvas>
|
<canvas id="overlay"></canvas>
|
||||||
<div id="debug"></div>
|
<div id="debug"></div>
|
||||||
|
<select id="camera-select" style="display:none;"></select>
|
||||||
</div>
|
</div>
|
||||||
<div id="bottom-panel">
|
<div id="bottom-panel">
|
||||||
<div id="cam-info" style="width:100%;font-size:11px;color:#5a6475;word-break:break-all;"></div>
|
<div id="cam-info" style="width:100%;font-size:11px;color:#5a6475;word-break:break-all;"></div>
|
||||||
<div id="result">Point camera at a barcode</div>
|
<div id="result">Point camera at a barcode</div>
|
||||||
<div id="focus-row">
|
<div id="focus-row">
|
||||||
<label style="display:flex;align-items:center;gap:4px;cursor:pointer;">
|
<button id="focus-auto-btn">Auto</button>
|
||||||
<input id="focus-auto" type="checkbox" checked>
|
|
||||||
Auto
|
|
||||||
</label>
|
|
||||||
<input id="focus-slider" type="range" min="0" max="1.62" step="0.01" value="0.3" disabled>
|
<input id="focus-slider" type="range" min="0" max="1.62" step="0.01" value="0.3" disabled>
|
||||||
<span id="focus-val">0.30m</span>
|
<span id="focus-val">0.30m</span>
|
||||||
</div>
|
</div>
|
||||||
<select id="camera-select" style="display:none; width:100%; padding:8px; background:#181e28; color:#dce4ef; border:1px solid #1e2a38; border-radius:8px; font-family:inherit;"></select>
|
|
||||||
<button id="start-btn">Start Camera</button>
|
<button id="start-btn">Start Camera</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user