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

上一篇文章中,我们讨论了一个自定义二维码填充函数,这个函数返回一个Byte型二位数组,数组的内容正是二维码矩阵的所有功能图形和格式版本信息,其中深色单元模块填充数字为1,浅色为0.最后,这个数组中还有大量的单元模块填充了数字3,这些模块就是为了数据填充做准备的。从本节开始,我们将要讨论二维码填充的最后一个部分:数据码字的填充。
数据码字除了包含我们已经完成编码的数据之外,还包含根据里德所罗门算法计算出来的数据纠错码,两部分按照一定的规律连接起来,就成为需要填充的数据码字。对于每个不同的版本,QR规范中都规定了不同的码字连接排列规则。对大部分版本的二维码来说,数据都是打乱顺序排列的,下面以版本“”为例,简单介绍数据码字的乱序排列方式。

我们要将下面的文字编码为一个6-H级别的二维码:
“月落乌啼霜满天,江枫渔火对愁眠
姑苏城外寒山寺,夜半钟声到客船”

首先将文字编码,采用中文汉字编码模式,编码后的数据码字为:

{209,15,139,10,176,222,114,232,54,244,171,101,209,96,203,69,97,168,162,74,130,243,25,148,181,99,200,2,21,162,30,116,39,10,20,25,220,39,141,105,227,128,65,151,9,139,5,72,201,175,5,188,76,97,82,192,236,17,236,17,236,17}
这个编码需要共有60个数据码字,按照QR码的版本和纠错级别,需要将60个数据码字分成四组,每一组15个码字,再分别计算RS(43,15),也就是说每一组有28个纠错码字。按照前面介绍的RS码计算方法计算出纠错码如下(数据码表示为 W1, W2, , ,纠错码字表示为 EC1, EC2,  ):

W1 W2 W3 W15 EC1 EC2 EC3 EC27 EC28
209 15 139 203 99 55 238 98 139
69 97 168 162 63 45 227 21 127
30 116 39 9 81 48 159 113 80
130 5 72 17 176 209 48 198 67

接下来,将上面的数据码字和纠错码字排列好后,从左上角开始从上到下,再从左到右逐列读取所有的数据,并重新排列:

{209,69,30,130,15,97,116,5,139,168,39,72,,203,162,9,17,99,63,81,176,55,45,48,209,238,227,159,209,,98,21,113,198,139,127,80,67}
这样形成了完整的数据码字序列,然后就可以按位逐个填入QR矩阵的数据码区域了。
数据码字的填充规则是这样的:首先从QR矩阵的右下角开始,从下至上地左右交替填充两列单元模块,到达矩阵的顶部后,再转而从上至下地填充相邻的两列单元模块,直至矩阵底部,然后再掉头向上填充,如此周而复始,直至完成整个矩阵的填充,具体的填充方式如下图所示:
这里写图片描述
如果使用Excel的工作表函数来实现上述矩阵的填充,将会使公示过于复杂,因此首选的方法还是使用VBA来进行填充。由于我们已经有了一个VBA自定义函数fillQR实现了一个二维数组的功能图形和版本信息的填充,因此对这个自定义函数稍加扩展便可以实现完整QR的填充。最简单的填充算法是所谓的“逐列法”,因为本来QR数据的填充就是逐列进行的,因此,如果令QR数组有S列,且QR数组中已经填好了功能图形和版本、格式信息,数据区域全部填充数字3。那么填充的算法为:
从第S列开始循环,循环步长为-2列
判断填充的方向,第奇数次填充方向为自底向上,偶数次填充方向为自顶向下
若填充方向为自底向上,从第S行开始循环,循环步长为-1:
如果当前列当前行是数据区域,则填充一个数据位,否则检查当前列-1列,如果是数据区域,则填充一个数据位
循环至下一行
循环至下一列

由于QR码的第7列被一列定位图形所占据,因此在采用逐列法填充的时候,循环开始从第S列填充至第9列后,应该跳过第7列,接下去直接从第6列开始填充,一直填充到第4和第2列,完成整个数据码填充。最后给出VBA代码,这段代码是自定义函数FillQR的一部分,FillQR已经接受了一个“0/1”字符串作为完整数据码字的输入,因此这段代码可以直接复制到FillQR函数中使用,接受FillQR的输入参数,从数据码字字符串中逐个取出字符值并完成QR矩阵的填充。

    x = 1
    j = Val(Mid(bs, x, 1))
    Direction = 1
    With currCell
        For .c = S To 9 Step -2
            If Direction = 1 Then
                For .r = S To 1 Step -1
                    If QRArray(.r, .c) = 3 Then
                        QRArray(.r, .c) = j Xor mask(m, .r, .c)
                        x = x + 1
                        j = Val(Mid(bs, x, 1))
                    End If
                    If QRArray(.r, .c - 1) = 3 Then
                        QRArray(.r, .c - 1) = j Xor mask(m, .r, .c - 1)
                        x = x + 1
                        j = Val(Mid(bs, x, 1))
                    End If
                Next
            Else
                For .r = 1 To S
                    If QRArray(.r, .c) = 3 Then
                        QRArray(.r, .c) = j Xor mask(m, .r, .c)
                        x = x + 1
                        j = Val(Mid(bs, x, 1))
                    End If
                    If QRArray(.r, .c - 1) = 3 Then
                        QRArray(.r, .c - 1) = j Xor mask(m, .r, .c - 1)
                        x = x + 1
                        j = Val(Mid(bs, x, 1))
                    End If
                Next
            End If
            Direction = IIf(Direction = 1, 2, 1)
        Next
        For .c = 6 To 2 Step -2
            If Direction = 1 Then
                For .r = S To 1 Step -1
                    If QRArray(.r, .c) = 3 Then
                        QRArray(.r, .c) = j Xor mask(m, .r, .c)
                        x = x + 1
                        j = Val(Mid(bs, x, 1))
                    End If
                    If QRArray(.r, .c - 1) = 3 Then
                        QRArray(.r, .c - 1) = j Xor mask(m, .r, .c - 1)
                        x = x + 1
                        j = Val(Mid(bs, x, 1))
                    End If
                Next
            Else
                For .r = 1 To S
                    If QRArray(.r, .c) = 3 Then
                        QRArray(.r, .c) = j Xor mask(m, .r, .c)
                        x = x + 1
                        j = Val(Mid(bs, x, 1))
                    End If
                    If QRArray(.r, .c - 1) = 3 Then
                        QRArray(.r, .c - 1) = j Xor mask(m, .r, .c - 1)
                        x = x + 1
                        j = Val(Mid(bs, x, 1))
                    End If
                Next
            End If
            Direction = IIf(Direction = 1, 2, 1)
        Next
    End With

另外还需要注意的是,QR的混码技术还没有用完,在填充数据码字的时候,数据位并不是直接填充到数组中的,而还需要进行一次掩码运算。QR规范中一共提供了8种不同的掩码,使用掩码的目的只是为了让整个QR矩阵中的黑白单元模块分布更加均匀。掩码的过程很简单,根据QR规范中提供的公式计算掩码值之后与数据码字进行Xor运算就可以了,掩码的信息已经包含在格式信息中了,解码时只要进行同样的Xor运算就可以了。上面的代码调用了mask函数来计算掩码值,并将异或运算结果填入矩阵。
掩码函数的代码如下:

Private Function mask(m As Long, row As Long, col As Long) As Long
' determine the value of mask pattern in the cord
' m is type of mask, value from 1 ~ 8
Dim x As Long
Dim y As Long

    x = col - 1
    y = row - 1
    Select Case m
    Case Is = 1
        mask = IIf((x + y) Mod 2 = 0, 1, 0)
    Case Is = 2
        mask = IIf(y Mod 2 = 0, 1, 0)
    Case Is = 3
        mask = IIf(x Mod 3 = 0, 1, 0)
    Case Is = 4
        mask = IIf((x + y) Mod 3 = 0, 1, 0)
    Case Is = 5
        mask = IIf(((y \ 2) + (x \ 3)) Mod 2 = 0, 1, 0)
    Case Is = 6
        mask = IIf((x * y) Mod 2 + (x * y) Mod 3 = 0, 1, 0)
    Case Is = 7
        mask = IIf(((x * y) Mod 2 + (x * y) Mod 3) Mod 2 = 0, 1, 0)
    Case Is = 8
        mask = IIf(((x * y) Mod 3 + (x + y) Mod 2) Mod 2 = 0, 1, 0)
    End Select
End Function

掩码的图形如下(图片来自QR规范):
这里写图片描述

在本文中,我们讨论了QR码数据填充和掩码的生成,使用“逐列法”算法进行了QR二位数组的填充。至此,我们已经完整地讨论了QR生成的全部过程。在下一篇文章也是最后一篇文章中,我们将讨论另一种数据码填充的算法“寻址法”,并结束整个系列文章

发布了15 篇原创文章 · 获赞 18 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Shepherdppz/article/details/78156343