# QR-Code-Generator & Webcam-Transfer – Statische Codes und optische Datenbrücke lokal im Browser

> **Autor:** Lukas Kopischke (Lead Software-Engineer, Düsseldorf)
> **Kanonische URL:** https://lukiluki.me/garden/qr-code-generator
> **Markdown-Endpunkt:** https://lukiluki.me/garden/qr-code-generator.md
> **Kategorie:** Web Standards & a11y | **Tags:** ISO/IEC 18004, Reed-Solomon, WebRTC, SVG, Offline-PWA
> **Veröffentlicht:** 2026-10-26 | **Aktualisiert:** 2026-10-26
> **Zusammenfassung:** Lokaler QR-Code-Generator ohne Ablaufdatum: Statische SVG- und PNG-Codes mit Kontrastprüfung erzeugen oder längere Texte per Webcam-QR-Stream übertragen.

---
Du druckst einen vermeintlich kostenlosen QR-Code auf 500 Einladungen oder Speisekarten – und zwei Wochen später zeigt der Scan plötzlich eine Bezahlschranke statt deiner Webseite. Oder du möchtest eine Konfigurationsdatei, einen öffentlichen SSH-Schlüssel oder ein WLAN-Passwort auf ein zweites Gerät bringen, ohne Cloud-Dienst, Messenger-Konto oder USB-Stick dazwischenzuschalten.

Ein statischer QR-Code nach ISO/IEC 18004 kodiert deine Zeichenkette samt Fehlerkorrektur-Bytes direkt als Schwarz-Weiß-Matrix im Bild und kommt ohne fremden Weiterleitungsserver aus. Für größere Textmengen, Fotos und Dateien bis 1 MB verpackt ein optischer Frame-Stream Dateiname, MIME-Typ und Rohbytes in nummerierte, per CRC-16 gesicherte Einzel-QR-Codes (bei Fotos auf Wunsch vorab lokal per Canvas-WebP auf rund 20–35 QR-Pakete geschrumpft), die eine Webcam in beliebiger Reihenfolge abfilmt und lokal zusammensetzt.

> **Merksatz:**
> Statische QR-Codes laufen niemals ab, weil die Zieladresse direkt im Punktmuster steckt statt hinter einer fremden Kurz-URL. Reicht ein einzelnes Quadrat für längere Texte oder Dateien nicht mehr aus, überträgt eine Schleife nummerierter QR-Frames die Daten rein optisch von Bildschirm zu Kamera.

## Warum manche „kostenlose“ QR-Codes ablaufen und wie die Matrix wirklich funktioniert

Ein QR-Code kennt technisch kein Verfallsdatum. Wenn ein gedruckter Code nach einer Testphase plötzlich den Dienst verweigert, hast du keinen direkten Link kodiert, sondern einen **dynamischen QR-Code**: Im Bild steckt dann eine fremde Kurz-URL wie `https://qr-anbieter.example/x7k9p`, die erst auf dem Server des Anbieters auf dein eigentliches Ziel weiterleitet. Schaltet der Betreiber diese Weiterleitung ab, wird jeder Druck unbrauchbar.

Kodierst du deine echte Zieladresse oder ein standardisiertes Schema wie `WIFI:T:WPA;S:MeinNetz;P:Geheim;;` direkt in die Matrix, entsteht ein **statischer QR-Code**. Solange deine eigene Zieladresse existiert, bleibt das Muster unbegrenzt lesbar – nachträglich ändern lässt sich das Ziel auf bereits gedrucktem Papier allerdings nicht mehr, sofern du keine Weiterleitung auf deiner eigenen Domain einrichtest.

### Fehlerkorrektur, Ruhezone und Polarität

Damit ein Code trotz Kratzern, Kaffeeflecken oder ungünstigem Kamerawinkel lesbar bleibt, ergänzt der Generator zusätzliche Prüfbytes über eine Reed-Solomon-Fehlerkorrektur (Mathematisches Verfahren über endlichen Körpern (Galois-Feldern), das verlorene oder verdeckte Datenblöcke aus redundanten Polynom-Stützstellen exakt rekonstruiert.). Vier Stufen sind im Standard festgelegt:

* **Stufe L (~7 % Redundanz):** Kleinste Modulanzahl; sinnvoll für lange URLs auf sauberen Bildschirmen.
* **Stufe M (~15 % Redundanz):** Ausgewogener Standard für Visitenkarten, Flyer und Web.
* **Stufe Q (~25 % Redundanz):** Robuste Wahl für Aufkleber, Werkstätten oder leicht gewölbte Flächen.
* **Stufe H (~30 % Redundanz):** Höchste Reserve, erzeugt aber deutlich kleinere Einzelpunkte und erfordert sauberen Druck.

Ebenso entscheidend für zuverlässiges Scannen sind die Quiet Zone (Ein mindestens vier Module breiter, unbedruckter heller Rand rund um das QR-Quadrat, damit der Scanner das Muster vom Hintergrund trennen kann.) sowie die Helligkeitspolarität: Der ISO-Standard erwartet **dunkle Module auf hellem Grund**. Viele Smartphone-Apps lesen heute zwar auch weiße Codes auf schwarzem Hintergrund, ältere Handscanner an Kassen oder Einlasskontrollen scheitern daran jedoch regelmäßig. Für verlässliche Lesbarkeit sollte das Kontrastverhältnis mindestens **4,5:1** betragen – dieselbe rechnerische Grundlage wie im Leitfaden zu [Accessibility & BITV 2.0](/garden/accessibility).

### Wie die optische QR-Datenbrücke Texte und Dateien ohne Startsignal überträgt

Theoretisch fasst ein einzelner QR-Code der Version 40 bis zu **2.953 Bytes**. Praktisch verschmelzen die winzigen Punkte auf normalen Laptop-Displays und Handykameras aber oft schon ab **350 bis 600 Bytes** zu einem kaum fokussierbaren Flimmern.

Um dennoch Konfigurationsdateien, Fotos, Screenshots oder Schlüssel rein optisch über einen Air Gap (Physikalische und logische Trennung zweier Systeme ohne gemeinsame WLAN-, Bluetooth-, Netzwerk- oder USB-Verbindung.) zu schicken, verpackt der Sender Dateiname, MIME-Typ und Rohbytes in einen kompakten Binär-Umschlag (`2 Bytes Header-Länge + JSON-Metadaten + Datei-Bytes`), teilt diesen in Pakete von 160 bis 950 Bytes auf und spielt sie mit bis zu 12 FPS als Endlosschleife ab. Große Kamerafotos (bis 25 MB) schrumpft eine vorgeschaltete `<canvas>`-WebP-Stufe direkt im Arbeitsspeicher auf rund 18 bis 30 KB – das entspricht bei `950 Bytes` Paketgröße nur etwa 20 bis 32 QR-Codes:

```typescript
// Binärer LKQR2-Umschlag für Texte & Dateien inklusive CRC-16-CCITT-Sicherung
export function crc16Ccitt(bytes: Uint8Array): string {
  let crc = 0xffff;
  for (let i = 0; i < bytes.length; i++) {
    crc ^= bytes[i] << 8;
    for (let bit = 0; bit < 8; bit++) {
      crc = (crc & 0x8000) !== 0 ? ((crc << 1) ^ 0x1021) & 0xffff : (crc << 1) & 0xffff;
    }
  }
  return crc.toString(16).padStart(4, "0");
}

export function buildOpticalFileFrames(
  fileName: string,
  mimeType: string,
  fileBytes: Uint8Array,
  maxChunkBytes = 160
): string[] {
  const headerBytes = new TextEncoder().encode(
    JSON.stringify({ k: "file", n: fileName, t: mimeType })
  );
  const envelope = new Uint8Array(2 + headerBytes.length + fileBytes.length);
  envelope[0] = (headerBytes.length >> 8) & 0xff;
  envelope[1] = headerBytes.length & 0xff;
  envelope.set(headerBytes, 2);
  envelope.set(fileBytes, 2 + headerBytes.length);

  const crcHex = crc16Ccitt(envelope);
  const sessionId = `${crcHex}${envelope.length.toString(36)}`;
  const total = Math.ceil(envelope.length / maxChunkBytes);

  return Array.from({ length: total }, (_, i) => {
    const slice = envelope.slice(i * maxChunkBytes, (i + 1) * maxChunkBytes);
    const base64 = btoa(String.fromCharCode(...slice));
    // Format: Protokoll | Sitzung | Paket/Gesamt | Gesamt-CRC16 | Base64-Paket
    return `LKQR2|${sessionId}|${i + 1}/${total}|${crcHex}|${base64}`;
  });
}
```

**Woher weiß der Empfänger, wo der Anfang ist?** Gar nicht über einen speziellen Start-Trigger, sondern über Selbstbeschreibung jedes einzelnen Bildes: Jedes QR-Paket trägt seine exakte Position (`4/12`), die Gesamtzahl (`12`) und die `Session-ID` im Klartext-Kopf. Schaltest du die Kamera mitten in der Schleife erst bei Paket `7` ein, legt der Browser sofort ein `Map`-Register für 12 Plätze an, liest `7..12` und im direkt anschließenden Schleifendurchlauf `1..6`. Sobald alle Plätze gefüllt sind, sortiert er die Blöcke numerisch von `1` bis `12`, prüft die `CRC-16`-Summe über den gesamten Datenstrom, liest ab Byte `0` den Dateinamen aus und bietet die Datei sofort zum Herunterladen an. Wie solche Werkzeuge dauerhaft als installierbare Web-App bereitstehen, vertieft [Offline-KI & lokale Web-Apps](/garden/offline-ai).

## Wann eignet sich ein QR-Code – und wann ist ein anderes Verfahren überlegen?

| Verfahren | Typische Datenmenge | Netzwerk nötig? | Stärken | Klare Grenze / Bessere Alternative |
| :--- | :--- | :--- | :--- | :--- |
| **Statischer Einzel-QR** | 20 – 350 Bytes | Nein (beim Erstellen & Scannen) | Unbegrenzt haltbar, kein Drittanbieter, in 0,2 s erfasst | Ziel nach dem Drucken nicht änderbar (außer über eigene Weiterleitungs-URL) |
| **Eigene Weiterleitungs-URL im QR** | 25 – 60 Bytes (`deine-domain.de/go`) | Beim Aufruf des Ziels | Kleine, extrem leicht scanbare Matrix + Ziel auf eigenem Server änderbar | Hängt dauerhaft vom Weiterbetrieb deiner eigenen Domain ab |
| **Optischer QR-Frame-Stream** | 0,4 – 150 KB (Rohdaten bis 1 MB) | **Komplett ohne Netz** (nur Bildschirm + Kamera) | Einweg-Übertragung für Texte, Configs & WebP-komprimierte Fotos (~25 QR-Codes) ohne Funk/Cloud | Begrenzte optische Bandbreite (~2–8 KB/s); ungeeignet für Videos oder große ZIP-Archive |
| **Lokales WLAN / WebRTC (z. B. LocalSend / PairDrop)** | Megabyte bis Gigabyte | Gleiches lokales Netz | Tausendfach schneller, bidirektional mit echter Dateiprüfung | Scheitert in getrennten Gäste-/Firmen-VLANs oder bei strikter Funk-Sperre |

  #### Empfohlen (Do): Saubere Praxis für Print & Transfer
* Für Drucksachen immer **SVG** exportieren, damit Kanten im Layoutprogramm gestochen scharf bleiben
    * Willst du ein Druckziel später ändern können, verlinke auf eine kurze Route deiner **eigenen** Domain statt auf fremde Gratis-Portale
    * Beim Kamera-Stream moderate `4 FPS` und `160 Bytes` Paketgröße wählen – das schlägt zu dichte Einzelbilder mit vielen Scan-Wiederholungen
  #### Vermeiden (Don't): Typische Fehlerquellen
* Keine fremden Kurz-URL-Generatoren für Schilder, Visitenkarten oder Vereinsflyer ohne Einsicht in die Ziel-URL verwenden
    * Keine hellen QR-Punkte auf dunklem oder stark gemustertem Foto-Hintergrund ohne helle Ruhezone platzieren
    * Keine sensiblen Zugangsdaten oder privaten Schlüssel in serverseitige Online-Generatoren kopieren

> **Einweg-Natur des optischen Streams beachten**
> Da die Kamera des Empfängers kein Funksignal an den Sender zurückschickt, weiß der sendende Bildschirm nicht, welches Paket dem Empfänger noch fehlt. Für Fotos empfiehlt sich deshalb die integrierte WebP-Stufe „Kompakt“ (~20–35 QR-Pakete, ein Umlauf dauert bei `8 FPS` nur ~3–4 Sekunden) statt unkomprimierter Megabyte-Originale.

## Prüfpunkte vor Druck oder Datenübertragung

- [ ] **Inhalt vor dem Druck gegenprüfen:** Scanne den fertigen Code mit der Standard-Kamera-App und lies die angezeigte Ziel-URL im Klartext – steht dort wirklich deine eigene Domain statt eines Weiterleitungsdienstes?
- [ ] **Mindestgröße und Kontrast einhalten:** Auf Papier mindestens `2 × 2 cm` Kantenlänge (bei langen vCards lieber `3 × 3 cm`), dunkle Module auf hellem Grund und mindestens `4,5:1` Kontrast wählen.
- [ ] **WLAN-Sonderzeichen testen:** Enthält dein WLAN-Passwort Semikolon (`;`), Doppelpunkt (`:`) oder Backslash (`\`), prüfe den Verbindungsaufbau einmal real an iOS und Android.
- [ ] **Optischen Stream blendfrei abfilmen:** Beim Webcam-Transfer Bildschirmhelligkeit erhöhen und schräges Gegenlicht vermeiden, damit der Belichtungsmesser der Kamera das Raster nicht überstrahlt.

## Quellen und nächste Schritte

* **ISO/IEC 18004:2024** – *Information technology — Automatic identification and data capture techniques — QR code bar code symbology specification*: [iso.org/standard/83389.html](https://www.iso.org/standard/83389.html)
* **ZXing Project Documentation** – *Barcode Contents Specification (`WIFI:`, `VCARD`, `mailto:` URI-Formate)*: [github.com/zxing/zxing/wiki/Barcode-Contents](https://github.com/zxing/zxing/wiki/Barcode-Contents)
* **MDN Web Docs** – *MediaDevices.getUserMedia() & lokale Kamera-Verarbeitung im Browser*: [developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia)
* **node-qrcode & jsQR (Open-Source-Referenzimplementierungen)** – [github.com/soldair/node-qrcode](https://github.com/soldair/node-qrcode) sowie [github.com/cozmo/jsQR](https://github.com/cozmo/jsQR)
* **Weiterführend im Digital Garden:** Lies [Accessibility & BITV 2.0](/garden/accessibility) zur exakten Berechnung von Luminanz-Kontrasten nach WCAG 2.2 oder [Offline-KI in Web-Apps](/garden/offline-ai) für den Service-Worker-Aufbau komplett lokal arbeitender PWAs.

---
*Quelle: [Lukas Kopischke – Digital Garden](https://lukiluki.me/garden/qr-code-generator) | Vollständiger Index: [llms.txt](https://lukiluki.me/llms.txt)*
