QR-Codes mit Access erzeugen, Teil II

Im ersten Teil dieser Beitragsreihe haben wir bereits den Ausdruck codiert, der später in Form eines QR-Codes grafisch abgebildet werden soll. Der weitaus interessantere Teil folgt noch: Die Berechnung des Fehlerkorrekturcodes. Schließlich folgt dann im letzten Teil der Beitragsreihe noch die Erstellung der eigentlichen Grafik, die Sie dann beispielsweise als Bilddatei speichern und etwa in einem Bericht einer Access-Datenbank weiterverwenden können.

Der Stand nach dem ersten Teil der Beitragsreihe ist, dass wir eine codierte Zeichenkette vorliegen haben – unabhängig davon, ob diese als Zahl, als reiner Text oder als alphanumerische Zeichenkette daherkommt. Außerdem haben wir eine Möglichkeit geschaffen, den Grad der Fehlerkorrektur auszuwählen und die Version, also die Kantenlänge des zu erzeugenden QR-Codes. Dieser wird schließlich bereits aus der Länge der zu kodierenden Zeichenkette, dem Fehlerkorrekturgrad und der Codiermethode ermittelt.

Als Nächstes benötigen wir den Fehlerkorrekturcode. Um diesen zu ermitteln, legen wir zunächst die Länge des Fehlerkorrekturcodes fest. Dies geschieht mithilfe einer Tabelle namens tblNumberOfDataCodewords (Entwurf siehe Bild 1). Die Tabelle liefert die folgenden wichtigen Informationen, wobei wir den Datensatz aus der Tabelle nutzen, dessen Felder VersionID und ErrorCorrectionLevelID für unsere Codierung zutreffen (s. Bild 2):

Entwurf der Tabelle tblVersions

Bild 1: Entwurf der Tabelle tblVersions

Die Tabelle tblVersions mit einigen Werten

Bild 2: Die Tabelle tblVersions mit einigen Werten

  • Wir teilen den Code für unseren Text in ein, zwei oder vier Blöcken mit einer bestimmten Anzahl von Codewörtern mit je acht binären Zeichen auf. Wenn NumberOfBlocksInGroup1 den Wert 1 enthält, verwenden wir nur einen Block, der die im Feld NumberOfDataCodewordsInGroup1Blocks angegebene Anzahl von Codewörtern enthält.
  • Wenn NumberOfBlocksInGroup1 den Wert 2 oder einen größeren Wert enthält, benötigen wir zwei oder mehr Blöcke mit der entsprechenden Anzahl von Codewörtern in der ersten Gruppe.
  • Es gibt noch eine zweite Gruppe, für welche die beiden Felder NumberOfBlocksInGroup2 und NumberOfDataCodewordsInGroup1Blocks die Anzahl der Blöcke und Codewörter angeben.

Wenn wir also beispielsweise für VersionID den Wert 5 und für die Fehlerkorrektur den Level Q ausgewählt haben, haben wir zwei Gruppen, wobei die erste zwei Blöcke mit je 15 Codewörtern aufnimmt und die zweite zwei Blöcke mit je 16 Codewörtern. Wenn Sie also beispielsweise einen Text codieren, der mit den oben genannten Parametern um einen Fehlerkorrekturcode erweitert werden soll, wird dieser Text 2 x 15 x 8 + 2 x 16 x 8 Zeichen lang sein, also 496 Zeichen lang sein – was auch der Fall ist. Wir brechen dies auf ein einfacheres Beispiel herunter, zum Beispiel zur Codierung der Zeichenkette HELLO WORLD. Wir verwenden den Fehlerkorrekturgrad M und die kleinste Version mit 21 x 21 Zeichen. Die codierte Zeichenfolge sieht nach der Beschreibung aus dem ersten Teil der Beitragsreihe so aus:

0010000001011011000010110111100011010001011100101101110001001101
0100001101000000111011000001000111101100000100011110110000010001

Die Tabelle tblNumberOfDataCodewords liefert die Information, dass diese Zeichenfolge in eine Gruppe mit 16 Blöcken á acht Zeichen aufgeteilt wird, also etwa so:

  • 00100000
  • 01011011
  • 00001011

Diese Zahlen benötigen wir später, wenn wir ein Polynom für den zu codierenden Text erstellen müssen. Die Zahlen dienen dann als Koeffizienten.

Steuerung der Codierung

Die Codierung starten Sie mit einem Klick auf die Schaltfläche cmdAktualisieren des Formulars frmQRCodes, das nun wie in Bild 3 aussieht. Dieses Formular erlaubt, wie schon im ersten Teil erläutert, die Eingabe des zu codierenden Textes in das Textfeld txtAusgangstext, die Auswahl des Zeichensatzes mit dem Kombinationsfeld cboModeID, den Grad der Fehlerkorrektur mit cboErrorCorrectionID und die Version beziehungsweise Kantenlänge mit cboVersionID.

Das Formular frmQRCodes

Bild 3: Das Formular frmQRCodes

Die Schaltfläche cmdAktualisieren löst nun die Prozedur aus Listing 1 aus. Diese erledigt zunächst die bereits im ersten Teil der Beitragsreihe beschriebenen Aufgaben, sprich: den binären Code zu ermitteln, welcher die Information über die Codierung, die Anzahl der Zeichen und den Text selbst in binärer Form enthält.

Private Sub cmdAktualisieren_Click()
     Dim strCode As String
     Dim lngBlocksGruppe1 As Long
     Dim lngWoerterGruppe1 As Long
     Dim lngBlocksGruppe2 As Long
     Dim lngWoerterGruppe2 As Long
     Dim lngVersionID As Long
     Dim lngErrorCorrectionLevelID As Long
     Dim db As DAO.Database
     Dim rst As DAO.Recordset
     Dim intAnzahlFehlerkorrekturWoerter  As Integer
     Set db = CurrentDb
     strCode = strCode & GetModeIndicator
     strCode = strCode & GetCharacterCountIndicator
     strCode = strCode & Encode(Me!txtAusgangstext, Me!cboModeID)
     strCode = AddPadBytes(strCode, Me!cboVersionID, Me!cboErrorCorrectionID)
     Me!txtCodeInhalt = strCode
     lngVersionID = Me!cboVersionID
     lngErrorCorrectionLevelID = Me!cboErrorCorrectionID
     Set rst = db.OpenRecordset("SELECT * FROM tblNumberOfDataCodewords " _
         & "WHERE VersionID = " & lngVersionID _
         & " AND ErrorCorrectionLevelID = " & lngErrorCorrectionLevelID)
     lngBlocksGruppe1 = rst!NumberOfBlocksInGroup1
     lngWoerterGruppe1 = rst!NumberOfDataCodewordsInGroup1Blocks
     lngBlocksGruppe2 = Nz(rst!NumberOfBlocksInGroup2, 0)
     lngWoerterGruppe2 = Nz(rst!NumberOfDataCodewordsInGroup2Blocks, 0)
     intAnzahlFehlerkorrekturWoerter = rst!ECCodewordsPerBlock
     Me!txtInterleavedCode = InterleavedCode(strCode, lngBlocksGruppe1, lngWoerterGruppe1, _
         lngBlocksGruppe2, lngWoerterGruppe2)
     Me!txtInterleavedErrorCorrectionCode = InterleavedErrorCode(strCode, lngBlocksGruppe1, _
         lngWoerterGruppe1, lngBlocksGruppe2, lngWoerterGruppe2, intAnzahlFehlerkorrekturWoerter)
     Me!txtQRCode = Me!txtInterleavedCode & Me!txtInterleavedErrorCorrectionCode
End Sub

Listing 1: Diese Prozedur steuert die komplette Codierung.

Dann speichert die Prozedur die Werte der beiden Kombinationsfelder cboVersionID und cboErrorCorrectionID in entsprechenden Variablen, bevor sie darauf basierend ein Recordset öffnet. Dieses enthält den Datensatz der Tabelle tblNumberOfDataCodewords, der den mit den Kombinationsfeldern ausgewählten Parametern entspricht. Aus dieser Tabelle entnehmen wir einige Werte und speichern diese in weiteren Variablen:

  • lngBlocksGruppe1
  • lngWoerterGruppe1
  • lngBlocksGruppe2
  • lngWoerterGruppe2
  • intAnzahlFehlerkorrekturWoerter

Nun folgt der interessante, in diesem Teil der Beitragsreihe beschriebene Part: Die Umwandlung des binären Codes in einen sogenannten “interleaved” Code und das Ermitteln und Hinzufügen des Fehlerkorrekturcodes.

Dies geschieht in folgenden Schritten:

  • Vermischen des Codes zum “interleaved” Code mit der Funktion InterleavedCode,
  • Ermitteln des Fehlerkorrekturcodes auf Basis des Ausgangscodes und Vermischen zum “interleaved” Fehlerkorrekturcode mit der Funktion InterleavedErrorCode und
  • Zusammenfügen der beiden Teile im Textfeld txtQRCode.

Schauen wir uns nun an, wie diese Funktionen arbeiten.

Code weiter bearbeiten

Den Code aus dem Textfeld txtCodeInhalt beziehungsweise der Variablen strCode verwenden wir nun nicht einfach so als ersten Teil des QR-Codes. Stattdessen wird dieser erste Teil gegebenenfalls noch etwas durcheinandergewürfelt – allerdings nach fest vorgegebenen Regeln. Dies geschieht allerdings nur ab einer gewissen Länge des Codes beziehungsweise wenn die einzelnen achtstelligen Binärzahlen auf mehr als nur die erste Gruppe aufgeteilt werden – also wenn in der Tabelle tbl-NumberOfDataCodewords in der Spalte für die aktuelle Konfiguration (aus Version und Fehlerkorrektur) auch ein Wert in den Spalten NumberOfBlocksInGroup2 beziehungsweise NumberOfDataCodewordsInGroup2Blocks vorliegen. Dies ist zum Beispiel der Fall, wenn Sie die Version 5 (37 x 37 Pixel) und den Fehlerkorrekturgrad Q wählen. Dieser Code hat insgesamt 62 Codewörter, wobei die erste Gruppe zwei Blocks mit je 15 Codewörtern enthält und die zweite Gruppe zwei Blocks mit je 16 Codewörtern. Die Prozedur InterleavedCode aus Listing 2 erwartet drei Parameter:

Public Function InterleavedCode(ByVal strCode As String, lngBlocksGruppe1 As Long, _
     lngWoerterGruppe1 As Long, lngBlocksGruppe2 As Long, lngWoerterGruppe2 As Long)
     Dim i As Integer
     Dim j As Integer
     Dim strTempNeu As String
     Dim strTemp As String
     Dim intCodewoerterMatrix() As Integer
     intCodewoerterMatrix = CodeWoerterMatrix(lngBlocksGruppe1, lngWoerterGruppe1, _
         lngWoerterGruppe2, lngBlocksGruppe2)
     strTemp = Trim(strCode)
     If lngWoerterGruppe2 <> 0 Then
         For j = 1 To lngWoerterGruppe2
             For i = 1 To lngBlocksGruppe1 + lngBlocksGruppe2
                 If intCodewoerterMatrix(i, j) <> 0 Then
                     strTempNeu = strTempNeu + Mid$(strTemp, intCodewoerterMatrix(i, j) * 8 - 7, 8)
                 End If
              Next i
         Next j
     End If
     If lngWoerterGruppe2 = 0 Then
         For j = 1 To lngWoerterGruppe1
             For i = 1 To lngBlocksGruppe1 + lngBlocksGruppe2
                 If intCodewoerterMatrix(i, j) <> 0 Then
                     strTempNeu = strTempNeu + Mid$(strTemp, intCodewoerterMatrix(i, j) * 8 - 7, 8)
                 End If
             Next i
         Next j
     End If
     InterleavedCode = strTempNeu
End Function

Listing 2: “Vermischen” des Binärcodes für den Ausgangstext

  • strCode: Code in binärer Form
  • lngBlocksGruppe1: Anzahl der Blöcke in der ersten Gruppe
  • lngWoerterGruppe1: Anzahl der Codewörter in der ersten Gruppe
  • lngBlocksGruppe2: Anzahl der Blöcke in der zweiten Gruppe (gegebenenfalls 0)
  • lngWoerterGruppe2: Anzahl der Codewörter in der zweiten Gruppe (gegebenenfalls 0)

Die hinteren vier Werte wurden von der aufrufenden Prozedur cmdAktualisieren aus der Tabelle tblNumberOfDataCodewords für die aktuelle Kombination aus Version und Fehlerkorrekturgrad ausgewählt.

Die Prozedur ruft zunächst eine weitere Routine auf, die eine wichtige Aufgabe beim Mischen der Codewörter erfüllt – sie erstellt nämlich ein Array, aus dem später die neue Reihenfolge für die Codewörter entnommen wird. Diese Prozedur heißt CodeWoerterMatrix und erwartet die soeben ermittelten vier Werte für die gleichnamigen Parameter lngBlocksGruppe1, lngWoerterGruppe1, lngWoerterGruppe2 und lngBlocksGruppe2 (s. Listing 3).

Public Function CodeWoerterMatrix(lngBlocksGruppe1 As Long, lngWoerterGruppe1 As Long, _
         lngWoerterGruppe2 As Long, lngBlocksGruppe2 As Long, strDataCodewords() As Variant)
     Dim i As Integer
     Dim j As Integer
     Dim intZaehler As Integer
     If lngWoerterGruppe2 <> 0 Then
         ReDim strDataCodewords(1 To lngBlocksGruppe1 + lngBlocksGruppe2, 1 To lngWoerterGruppe2)
         For j = 1 To lngWoerterGruppe2
             For i = 1 To lngBlocksGruppe1 + lngBlocksGruppe2
                 strDataCodewords(i, j) = 0
             Next i
         Next j
         intZaehler = 1
         For i = 1 To lngBlocksGruppe1
             For j = 1 To lngWoerterGruppe1
                 strDataCodewords(i, j) = intZaehler
                 intZaehler = intZaehler + 1
             Next j
         Next i
         For i = lngBlocksGruppe1 + 1 To lngBlocksGruppe1 + lngBlocksGruppe2
             For j = 1 To lngWoerterGruppe2
                 strDataCodewords(i, j) = intZaehler
                 intZaehler = intZaehler + 1
             Next j
         Next i
     Else
         ReDim strDataCodewords(1 To lngBlocksGruppe1 + lngBlocksGruppe2, 1 To lngWoerterGruppe1)
         For j = 1 To lngWoerterGruppe1
             For i = 1 To lngBlocksGruppe1
                 strDataCodewords(i, j) = 0
             Next i
         Next j
         intZaehler = 1
         For i = 1 To lngBlocksGruppe1
             For j = 1 To lngWoerterGruppe1
                 strDataCodewords(i, j) = intZaehler
                 intZaehler = intZaehler + 1
             Next j
         Next i
     End If
End Function

Listing 3: Hilfsfunktion zum vermischen der Codewörter

Wenn die zweite Gruppe aus der Tabelle tblNumberOfDataCodewords mindestens einen Block enthält, ist der erste Teil der If…Then-Bedingung der Prozedur CodeWoerterMatrix erfüllt. Dieser Teil füllt nun beispielsweise die laufenden Nummern (im Falle von Version 5, Fehlerkorrekturlevel Q von 1 bis 62) in ein zweidimensionales Array namens intCodewoerterMatrix(). Dieses wird zuvor mit der ReDim-Anweisung mit dem Bereich 1 bis 2 + 2 für die erste Dimension und dem Bereich 1 bis 16 für die zweite Dimension dimensioniert. Die Zahlen werden dann einfach wie folgt eingetragen – also in vier Zeilen und 16 Spalten wie in der folgenden schematischen Darstellung:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 0
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62

Zuvor wird das Array noch komplett mit Nullen gefüllt. Sollte die Tabelle tblNumberOfDataCodewords keinen einzigen Block in der Zeile NumberOfBlocksInGroup2 für die gewählte Konfiguration aus Version und Fehlerkorrekturgrad liefern, führt die Prozedur CodeWoerterMatrix den zweiten Teil der If…Then-Bedingung aus. In diesem Fall legt die Prozedur nur für die Elemente der ersten Gruppe die laufenden Nummern der Codewörter an. In unserem einfachen Beispiel von oben sieht dies so aus – also nur mit einer Zeile und 16 Spalten:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

Das entsprechende Array intCodewoerterMatrix() wird nun als Rückgabewertan die aufrufende Prozedur zurückgegeben. Diese speichert nun zunächst den Code aus dem Parameter strCode in der Variablen strTemp. Wenn die aktuelle Konfiguration Wörter in der zweiten Gruppe hergibt, werden die Anweisungen in der ersten If…Then-Bedingung ausgeführt. Hier werden zwei For…Next-Schleifen durchlaufen, die äußere über die Anzahl der Wörter der zweiten Gruppe (hier 16), die innere die Anzahl der Blocks der ersten Gruppe plus die der zweiten Gruppe (hier 2 + 2). Sollte das Array intCodewoerterMatrix für die Laufvariablen i und j einen Wert ungleich 0 enthalten, beginnt die Prozedur mit dem Erstellen einer neuen Code-Zeichenkette in der Variablen strTempNeu. Dabei wird jeweils der Teil bestehend aus acht Zeichen aus strTemp eingelesen und an strTempNeu angehängt, welcher der laufenden Nummer des Elements von intCodewoerterMatrix() mit den Werten für i und j entspricht.

Das heißt, dass das Array nun nicht zunächst spalten- und dann zeilenweise durchlaufen wird (also 1, 2, 3 und so weiter), sondern erst zeilen- und dann spaltenweise. Die Reihenfolge der neu zusammengesetzten Elemente lautet also:

1, 16, 31, 47, 2, 17, 32, 48, 3, 18, 33, 49, 4, 19, 34, 50, 5, 20, 35, 51, 6, 21, 36, 52, 7, 22, 37, 53, 8, 23, 38, 54, 9, 24, 39, 55, 10, 25, 40, 56, 11, 26, 41, 57, 12, 27, 42, 58, 13, 28, 43, 59, 14, 29, 44, 60, 15, 30, 45, 61, 46, 62.

Sprich: Der Ausdruck in strTempNeu beginnt mit dem ersten Codewort aus strTemp, dann folgt das 16. Codewort, dann das 31. und so weiter.

Sollte die zweite Gruppe keinen einzigen Block enthalten, durchläuft die Prozedur wiederum in zwei Schleifen die Werte des Arrays intCodewoerterMatrix(). Wenn intCodewoerterMatrix an dieser Stelle nur eine Zeile mit Daten enthält, ist strTempNeu gleich der mit strCode übergebenen Zeichenkette. Das Ergebnis wird dann als Ergebnis der Funktion InterleavedCode an die aufrufende Routine zurückgeliefert.

Fehlerkorrekturcode ermitteln

Der Fehlerkorrekturcode wird nach einer recht komplizierten Vorgehensweise erstellt. Dafür benötigen wir zwei Polynome – eines für den zu verschlüsselnden Text und ein sogenanntes “generator polynomial”.

Das Polynom für den zu verschlüsselnden Text erzeugen wir auf Basis der oben ermittelten Codewörter.

Ausgangspunkt für die Erstellung des Fehlerkorrekturcodes ist die Funktion InterleavedErrorCode aus Listing 4. Sie erwartet die gleichen Parameter wie die oben beschriebene Funktion InterleavedCode – plus einen weiteren, nämlich intAnzahlFehlerkorrekturwoerter. Dies ist ein weiterer Wert, der bereits in cmdAktualisieren_Click für die aktuelle Konfiguration aus Version und Fehlerkorrekturgrad aus der Tabelle tblNumberOfDataCodewords ermittelt wurde – und zwar aus dem Feld ECCodewordsPerBlock. Dieser Wert gibt an, wie viele binäre Wörter aus acht Zeichen noch an die bereits ermittelte Zeichenkette angehängt werden müssen, um den Code für den QR-Code zu vervollständigen. Das Verhältnis der Anzahl der Fehlerkorrekturwörter zu den Codewörtern steigt mit wachsendem Fehlerkorrekturgrad.

Public Function InterleavedErrorCode(ByVal strCode As String, lngBlocksGruppe1 As Long, _
         lngWoerterGruppe1 As Long, lngBlocksGruppe2 As Long, lngWoerterGruppe2 As Long, _
         intAnzahlFehlerkorrekturWoerter As Integer)
     Dim i As Integer, j As Integer, intStart As Integer, intEnde As Integer
     Dim intMessagePolynomial() As Integer, intAnzahlKoeffizientenMessage As Integer, intBlock As Integer
     Dim strTemp As String, strCodeTeil As String, strTempInterleaved As String
     Dim intFehlerwoerterMatrix() As Variant, intCodeTeil As Integer
     intFehlerwoerterMatrix() = FehlerwoerterMatrix(lngBlocksGruppe1, lngBlocksGruppe2, _
         intAnzahlFehlerkorrekturWoerter)
     intStart = 1
     intEnde = lngWoerterGruppe1 * 8
     intBlock = 0
     Do
         intAnzahlKoeffizientenMessage = 1
         If intBlock < lngBlocksGruppe1 Then
             For i = intStart To intEnde Step 8
                 strCodeTeil = Mid$(strCode, i, 8)
                 intCodeTeil = BinaerZuDezimal(strCodeTeil)
                 ReDim Preserve intMessagePolynomial(intAnzahlKoeffizientenMessage) As Integer
                 intMessagePolynomial(intAnzahlKoeffizientenMessage) = intCodeTeil
                 intAnzahlKoeffizientenMessage = intAnzahlKoeffizientenMessage + 1
             Next i
             intStart = intEnde + 1
             If intBlock = lngBlocksGruppe1 - 1 Then
                 intEnde = intEnde + lngWoerterGruppe2 * 8
             Else
                 intEnde = intEnde + lngWoerterGruppe1 * 8
             End If
         End If
         If intBlock >= lngBlocksGruppe1 Then
             For i = intStart To intEnde Step 8
                 strCodeTeil = Mid$(strCode, i, 8)
                 intCodeTeil = BinaerZuDezimal(strCodeTeil)
                 ReDim Preserve intMessagePolynomial(intAnzahlKoeffizientenMessage) As Integer
                 intMessagePolynomial(intAnzahlKoeffizientenMessage) = intCodeTeil
                 intAnzahlKoeffizientenMessage = intAnzahlKoeffizientenMessage + 1
             Next i
             intStart = intEnde + 1
             intEnde = intEnde + lngWoerterGruppe2 * 8
         End If
     ...

Listing 4: Erstellen des Fehlerkorrekturcodes (Teil I)

Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...

den kompletten Artikel im PDF-Format mit Beispieldatenbank

diesen und alle anderen Artikel mit dem Jahresabo

Schreibe einen Kommentar