基于Excel的QR二维码生成工具——原理及算法详解(之六)

前五篇文章中,我们讨论了伽罗华域以及里德所罗门纠错码的生成、二维码的数据编码等问题;在生成二维码的过程中,完成数据编码并计算纠错码后,工作已经结束了80%,剩下的部分就是数据填充和掩码了。因此,从本节开始,我们就集中精力讨论数据填充的问题。

根据QR规范,一个二维码图像填充区域是一个划分为若干小方块的正方形矩阵,矩阵中每一个单元小方块都可以填充为深浅两种颜色,计算机在读码时会将深色方块解读为1,浅色方块解读为0. 二维码的尺寸大小(矩阵中单元方块的数量)并不是一定的,共有四十个不同的尺寸,官方称谓为版本,从版本1一直到版本40,每个版本的二维码矩阵边长包含小方块的数量可以用公式 s=4v+17 来计算,其中 v 为版本号,s为正方形矩阵边长包含小方块的数量。最大的版本40含有177X177个单元方块。

这里写图片描述
另外,在二维码中,不是所有的方块都可以填充数据的,每个版本都预留了一定的区域用来填充所谓“功能图形”,以便实现图像定位、版本查询等功能。上图列出了所有相关的功能图形:

  • Finder Pattern:位置探测图形,用于扫描时定位整个图像区域
  • Separator :位置探测图形分隔符,用于将位置探测图形与数据区域分开
  • Timing Pattern:定位图形,用来帮助定位矩阵中的单元小方块
  • Alignment Pattern:矫正图形,当矩阵码尺寸较大的时候需要,用来减少扫描图形形变造成的误码
  • Format Information:格式信息,以BCH纠错码的形式储存二维码的纠错级别、以及掩码形式的信息
  • Version Information:当版本大于7时需要用到,储存了二维码的版本信息

在二维码图形区域中,除了上述功能和信息图形以外,其他所有的区域就都是数据区域了。在本文中,我们将首先探讨如何完成二维码功能和信息图形的填充
在Excel中,利用工作表的条件格式,我们可以很容易地在一个单元格区域中通过填充数字1或者0来实现二维码填充的效果,我们只需要选择一个长和宽都为177个单元格的区域,将行高和列宽都设置为同样的尺寸,比如15像素,那么就可以得到一个正方形的填充区域,接下来,我们就可以使用矩阵工作表函数来填充这个单元格区域,从而获得一个二维码图形。由于使用工作表函数来填充二维码太过于复杂而难以实现,因此较为理想的做法是使用VBA创建一个自定义工作表函数,并使用这个函数来填充二维码,另外,为了使填充的速度加快,我们需要避免在VBA中频繁地访问单元格或填充单元格,因此应该将这个自定义函数的返回值定义为一个二位数组,那么只需要将这个函数的返回值一次填充到单元格中即可。
为了方便条件格式的使用,我们希望在单元格中填写的是数字0或1,另外,为了区别数据填充区域和其他功能图形区域,我们也希望单元格中可以填写其他的数字例如3;同时,为了尽量降低内存的需求,我们可以将填充矩阵的数据类型定义为Byte型,因此,定义一个Byte型的二维数组就可以胜任二维码填充的任务了。同时,为了方便功能图形的填充,还可以定义几个简单的函数:

Private Function fillSquare(canvas() As Byte, row As Long, col As Long, size As Long, value As Byte)
' basic filling function that fills a certain sized square area in the array canvas on designated position
' canvas is the array in which value is written
' row is the row offset counting from top left corner which is (1,1)
' col is the column offset counting from top left corner which is (1, 1)
' size defines the length of side of square
' value can be 1 or 0
Dim i As Long
Dim j As Long

    For i = 1 To size
        For j = 1 To size
            canvas(row + i - 1, col + j - 1) = value
        Next
    Next
End Function

上面的函数在数据填充区域以任意制定值填充任意大小的正方形区域,功能图形的填充会大量用到这个函数,在它基础上可以容易定义出位置探测图形的填充函数:

Private Function fillFinder(arr() As Byte, row As Long, col As Long, Optional fType As Byte = qrTopLeft)
' fills a finder pattern in the array on designated position
' row and col defines the top left corner of finder pattern
' ftype is a ennumerate that can be "qrTopLeft", "qrTopRight" or "qrBottomLeft" defining position of the finder pattern
    Select Case fType
    Case Is = qrTopLeft
        fillSquare arr, row, col, 8, 0
        fillSquare arr, row, col, 7, 1
        fillSquare arr, row + 1, col + 1, 5, 0
        fillSquare arr, row + 2, col + 2, 3, 1
    Case Is = qrTopRight
        fillSquare arr, row, col - 1, 8, 0
        fillSquare arr, row, col, 7, 1
        fillSquare arr, row + 1, col + 1, 5, 0
        fillSquare arr, row + 2, col + 2, 3, 1
    Case Is = qrBottomLeft
        fillSquare arr, row - 1, col, 8, 0
        fillSquare arr, row, col, 7, 1
        fillSquare arr, row + 1, col + 1, 5, 0
        fillSquare arr, row + 2, col + 2, 3, 1
        fillSquare arr, row - 1, col + 8, 1, 1  ' fill the last "1" unit
    End Select
End Function

其中的输入参数ftype代表位置探测图形所在的位置,因为根据位置不同,位置探测图形分隔符的位置也是不同的,三个位置探测图形需要分别区别对待。同样也可以很容易写出矫正图形的填充函数:FillAlignment(),源码很简单就不赘述了。以上面的函数为基础我们可以编写二维码数据填充函数如下:

Private Function fillQR(bs As String, fi As String, v As Long, S As Long, Optional apc As Range, Optional m As Long = 0, Optional vi As String)
' fills an array with bit streams to form a complete QR code, and return the whole array
' bs: A stream of string that contains only "1" or "0" that represent the complete code word flow including error correction codes
' fi: a stream of string that contains format information of QR
' v:  An long that represents the version of QR
' s:  An long defines the size of a QR
' apc: alignment pattern center coordinates, stored in a range, can be converted automatically
' m:  A long type variant defines the type of mask
' vi: Version information, optional argument, which is needed only for QR larger than version 6

Dim QRArray() As Byte

Dim fp(1 To 3) As finderPattern
Dim ap() As alignmentPattern
Dim apCord(1 To 7) As Variant
Dim num As Variant
Dim currCell As coordinate
Dim QRCopy As Range

Dim Direction As Byte
Dim x As Long
Dim i As Long
Dim j As Long
Dim k As Long

    ReDim QRArray(1 To S, 1 To S) ' re define the array, size calculated from version by size=17+4*version
    fillSquare QRArray, 1, 1, S, 3
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'   1    locate and fill three finder patterns
    For i = 1 To 3
        With fp(i)
            .fRow = IIf(i = 3, S - 6, 1)
            .fCol = IIf(i = 2, S - 6, 1)
            .fType = i
        End With
        fillFinder QRArray, fp(i).fRow, fp(i).fCol, fp(i).fType
    Next
'        all finder patterns are filled
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'   2     locate and fill all alignment patterns
    i = 1
    For Each num In apc
        If IsNumeric(num) Then
            apCord(i) = num
            i = i + 1
        End If
    Next num
    If v > 1 Then
        x = Fix(v / 7) + 2 ' determin count of ap coordinates
        ReDim ap(1 To x ^ 2 - 3) ' determin count of aliment patterns
        k = 1
        For i = 1 To x
            For j = 1 To x
                '  assign coordinates to each ap()
                If Not ((i = 1 And j = 1) Or (i = 1 And j = x) Or (i = x And j = 1)) Then  ' set topleft / topright and bottomleft coordinates as expectional
                    With ap(k)
                        .aRow = apCord(i)
                        .aCol = apCord(j)
                        fillAlignment QRArray, .aRow, .aCol
                        k = k + 1
                    End With
                End If
            Next
        Next
    End If
'        alignment patterns are filled
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'   3     Fill the timing pattern
    For i = 0 To 4 * v Step 2
        QRArray(7, 9 + i) = 1 ' fill horizontal timing pattern
        QRArray(7, 10 + i) = 0

        QRArray(9 + i, 7) = 1  'fill vertical timing pattern
        QRArray(10 + i, 7) = 0
    Next

'         timing pattern is filled
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'   4     Fill the format info
    For i = 1 To 6  'format info has always 15 bits, should be divided into three parts
        QRArray(9, i) = Val(Mid(fi, i, 1))   'horizontal format info
        QRArray(18 + 4 * v - i, 9) = Val(Mid(fi, i, 1)) ' vertical format info
    Next
    i = 7
        QRArray(9, i + 1) = Val(Mid(fi, i, 1))
        QRArray(18 + 4 * v - i, 9) = Val(Mid(fi, i, 1))
    For i = 8 To 9
        QRArray(9, i + 2 + 4 * v) = Val(Mid(fi, i, 1))
        QRArray(17 - i, 9) = Val(Mid(fi, i, 1))
    Next
    For i = 10 To 15
        QRArray(9, i + 2 + 4 * v) = Val(Mid(fi, i, 1))
        QRArray(16 - i, 9) = Val(Mid(fi, i, 1))
    Next
'         Format information is filled
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'   5     Fill the Version info if version is larger than 7
    If v >= 7 Then ' length of version info is always 18, and is filled in a 3X6 array
        k = 1
        For j = 6 To 1 Step -1
            For i = 3 To 1 Step -1
                QRArray(6 + 4 * v + i, j) = Val(Mid(vi, k, 1))
                QRArray(j, 6 + 4 * v + i) = Val(Mid(vi, k, 1))
                k = k + 1
            Next
        Next
    End If
'         Version info is filled
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'   6     Fill the Data Codes, to be explained in next chapter

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    fillQR = QRArray 'assign filled array to function
End Function

上面的函数接受二维码的一些基本数据作为参数,如版本号、尺寸、格式信息、版本信息、数据编码等,然后根据二维码的版本生成一个合适大小的byte型二维数组,并将所有功能图形、格式信息、版本信息等数据填充到二位数组中,深色单元填充数字1,浅色单元填充数字0,而所有数据填充区域填充数字3. 完成矩阵填充后,将整个数组作为返回值,在工作表中调用这个函数的时候,则需要用矩阵函数的调用方法,在一个区域中直接写入函数,并用“Alt+Ctrl+Enter”来输入函数,这时Excel会自动将二位数组中的数据逐行逐列地填入相应的单元格中,这时单元格公式被一对大括号“{}”所包围
这里写图片描述

至此,我们就完成了功能和格式信息的填充。不过,我们忽略了一个问题:格式信息和版本信息的BCH编码应该怎么进行呢?格式信息中包括纠错等级和掩码格式两部分,分别包含2个二进制位和三个二进制位,编码分别如下:
纠错等级的指示符

纠错等级LMQH
二进制指示符01001110

掩码的二进制指示符

掩码二进制指示符掩码生成公式
000 (i+j) mod 2=0
001 I mod 2=0
010 j mod 3=0
011 (i+j) mod 3=0
100 ((I div 2)+(j div 3)) mod 3=0
101 (ij) mod 2+(ij) mod 3=0
110 ((ij) mod 2+(ij) mod 3) mod 2=0
111 ((ij) mod 2+(i+j) mod 3) mod 2=0

选择好QR的纠错容量和掩码后,将它们的指示符连接起来,形成一个5位二进制数,接下来需要计算它的BCH纠错码。BCH纠错码的计算过程与RS纠错码非常类似,区别在于RS纠错码针对的是0~255的数字二BCH码直接在二进制数的位上进行。BCH码也有生成多项式,它的计算方法与RS码类似,用数据码多项式对生成多项式求余,得到的结果就是纠错码。QR规范中给出了BCH码的生成多项式,我们可以借用本文前两节中讨论的RS码计算工作表来计算BCH纠错码。
例如,对于格式信息“10101“,我们要计算的BCH码是BCH(15,5),与RS码类似,它表示数据码是5位,而纠错码是10位,生成多项式是

g(x)=x10+x8+x5+x4+X2+x1+1
,因此我们需要建立一个15位的除法计算表:

多项式系数151413121110987654321
生成多项式 g(x) 10100110111
数据码多项式00101
乘数1* g(x) 10100110111
余数000011101110000
乘数0* g(x) 00000000000
余数00011101110000

利用上面的除法工作表可以计算出纠错码为“1001000111“,但是还没完,与格式信息”10101“连接起来后还需要与“101010000010010”进行异或运算,这才得到最终的格式信息结果。版本信息的计算方法与格式信息完全一致,只不过使用了BCH(18,6)码进行计算,它的生成多项式为

g(x)=x12+x11+x10+x9+x8+x5+x2+1
具体计算表就不再赘述。

至此,我们已经完成了相当大部分的工作:完成了二维码的编码、最重要的RS码计算,格式信息和版本信息的BCH纠错码计算,并且在一个二位数组中填充了所有的功能图形和格式、版本信息。从下一节开始,我们将讨论最后的部分:数据码的填充。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值