diff --git a/src/barcode.js b/src/barcode.js new file mode 100644 index 0000000..3b4fe9b --- /dev/null +++ b/src/barcode.js @@ -0,0 +1,52 @@ +import { readBarcodes, prepareZXingModule, purgeZXingModule } from 'zxing-wasm/reader'; + +// Vorbereitung des WebAssembly-Moduls ist teuer und darf nur einmal +// laufen. Statt eines einfachen Merkers wird das Promise selbst +// zwischengespeichert: so teilen sich auch mehrere schnell +// hintereinander gestartete Dekodierungen dieselbe, bereits laufende +// Vorbereitung, statt sie erneut anzustossen (kein Race zwischen +// "noch nicht fertig" und "schon gestartet"). +let vorbereitungsPromise = null; + +async function ensureReady() { + if (!vorbereitungsPromise) { + vorbereitungsPromise = prepareZXingModule({ fireImmediately: true }).catch((error) => { + // Scheitert die Vorbereitung (z. B. WASM-Datei nicht ladbar), darf der + // Adapter nicht dauerhaft unbrauchbar bleiben. zxing-wasm cacht das + // fehlgeschlagene Promise intern selbst und wuerde es sonst bei jedem + // weiteren Versuch unveraendert zurueckgeben - deshalb hier den + // internen Cache leeren und den eigenen Merker zuruecksetzen, damit + // der naechste Aufruf einen echten neuen Versuch macht. + purgeZXingModule(); + vorbereitungsPromise = null; + throw error; + }); + } + await vorbereitungsPromise; +} + +/** + * Dekodiert Code-128 und DataMatrix aus einem Einzelbild. + * Barcode-Dekodierung ist exakt - deshalb ist sie die bevorzugte Quelle + * gegenueber der fehleranfaelligen Texterkennung. + * + * Liefert nur die dekodierten Zeichenketten, keine fachliche Logik. + * Ein leeres Ergebnis (kein Code im Bild gefunden) ist ein regulaerer + * Ausgang und kein Fehler. + * + * @param {ImageData} imageData Einzelbild aus der Kamera. + * @returns {Promise} + */ +export async function decodeBarcodes(imageData) { + await ensureReady(); + const results = await readBarcodes(imageData, { + formats: ['Code128', 'DataMatrix'], + tryHarder: true, + maxNumberOfSymbols: 4, + }); + // readBarcodes liefert standardmaessig (returnErrors: false) nur gueltige + // Treffer; der Text-Filter faengt zusaetzlich den seltenen Fall eines + // leeren Inhalts ab, damit garantiert keine leeren Zeichenketten + // durchgereicht werden. + return results.map((result) => result.text).filter((text) => text.length > 0); +} diff --git a/src/main.js b/src/main.js index 52d47e4..d16c5f6 100644 --- a/src/main.js +++ b/src/main.js @@ -1,4 +1,5 @@ import { startCamera, grabFrame } from './camera.js'; +import { decodeBarcodes } from './barcode.js'; const app = document.querySelector('#app'); app.innerHTML = ` @@ -14,7 +15,15 @@ startCamera(video) .then(() => { out.textContent = 'Kamera laeuft'; }) .catch((error) => { out.textContent = `Kamera nicht verfuegbar: ${error.message}`; }); -document.querySelector('#b').addEventListener('click', () => { - const frame = grabFrame(video); - out.textContent = `Bild: ${frame.width}x${frame.height}`; +document.querySelector('#b').addEventListener('click', async () => { + try { + // grabFrame wirft, wenn das Kamerabild noch nicht bereit ist (siehe + // camera.js) - dieser Fall wird hier abgefangen und sichtbar gemacht, + // statt unbehandelt zu verpuffen. + const frame = grabFrame(video); + const codes = await decodeBarcodes(frame); + out.textContent = codes.length ? codes.join('\n') : 'kein Barcode gefunden'; + } catch (error) { + out.textContent = `Fehler: ${error.message}`; + } });