feat: Barcode-Adapter fuer Code-128 und DataMatrix

Dekodiert Code-128 und DataMatrix aus einem Einzelbild ueber zxing-wasm.
Statt eines einfachen Boolean-Merkers wird die einmalige WASM-Vorbereitung
per gecachtem Promise realisiert (verhindert doppelte Vorbereitung bei
schnell aufeinanderfolgenden Aufrufen) und im Fehlerfall via
purgeZXingModule() zurueckgesetzt, damit ein spaeterer Versuch echt neu
startet statt eine intern gecachte Ablehnung zu wiederholen.

main.js: Klick-Handler zeigt die dekodierten Codes an und faengt jetzt
auch den Fehler ab, den grabFrame wirft, wenn das Kamerabild noch nicht
bereit ist.
This commit is contained in:
vchuser
2026-07-28 16:34:12 +02:00
parent 1192a898c4
commit 87d7f7c393
2 changed files with 64 additions and 3 deletions
+52
View File
@@ -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<string[]>}
*/
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);
}
+12 -3
View File
@@ -1,4 +1,5 @@
import { startCamera, grabFrame } from './camera.js'; import { startCamera, grabFrame } from './camera.js';
import { decodeBarcodes } from './barcode.js';
const app = document.querySelector('#app'); const app = document.querySelector('#app');
app.innerHTML = ` app.innerHTML = `
@@ -14,7 +15,15 @@ startCamera(video)
.then(() => { out.textContent = 'Kamera laeuft'; }) .then(() => { out.textContent = 'Kamera laeuft'; })
.catch((error) => { out.textContent = `Kamera nicht verfuegbar: ${error.message}`; }); .catch((error) => { out.textContent = `Kamera nicht verfuegbar: ${error.message}`; });
document.querySelector('#b').addEventListener('click', () => { document.querySelector('#b').addEventListener('click', async () => {
const frame = grabFrame(video); try {
out.textContent = `Bild: ${frame.width}x${frame.height}`; // 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}`;
}
}); });