基于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}
<script type="math/tex; mode=display" id="MathJax-Element-193">\{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\}</script>这个编码需要共有60个数据码字,按照QR码的版本和纠错级别,需要将60个数据码字分成四组,每一组15个码字,再分别计算RS(43,15),也就是说每一组有28个纠错码字。按照前面介绍的RS码计算方法计算出纠错码如下(数据码表示为 W1, W2, , <script type="math/tex" id="MathJax-Element-194">W_1,\ W_2,\ \cdots, </script>,纠错码字表示为 EC1, EC2,  <script type="math/tex" id="MathJax-Element-195">EC_1,\ EC_2,\ \cdots</script>):

W1 <script type="math/tex" id="MathJax-Element-196">W_1</script> W2 <script type="math/tex" id="MathJax-Element-197">W_2</script> W3 <script type="math/tex" id="MathJax-Element-198">W_3</script> <script type="math/tex" id="MathJax-Element-199">\cdots</script> W15 <script type="math/tex" id="MathJax-Element-200">W_15</script> EC1 <script type="math/tex" id="MathJax-Element-201">EC_1</script> EC2 <script type="math/tex" id="MathJax-Element-202">EC_2</script> EC3 <script type="math/tex" id="MathJax-Element-203">EC_3</script> <script type="math/tex" id="MathJax-Element-204">\cdots</script> EC27 <script type="math/tex" id="MathJax-Element-205">EC_27</script> EC28 <script type="math/tex" id="MathJax-Element-206">EC_28</script>
20915139 <script type="math/tex" id="MathJax-Element-207">\cdots</script>2039955238 <script type="math/tex" id="MathJax-Element-208">\cdots</script>98139
6997168 <script type="math/tex" id="MathJax-Element-209">\cdots</script>1626345227 <script type="math/tex" id="MathJax-Element-210">\cdots</script>21127
3011639 <script type="math/tex" id="MathJax-Element-211">\cdots</script>98148159 <script type="math/tex" id="MathJax-Element-212">\cdots</script>11380
130572 <script type="math/tex" id="MathJax-Element-213">\cdots</script>1717620948 <script type="math/tex" id="MathJax-Element-214">\cdots</script>19867

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

{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}
<script type="math/tex; mode=display" id="MathJax-Element-215">\{209,69,30,130,15,97,116,5,139,168,39,72,\cdots,203,162,9,17,99,63,81,176,55,45,48,209,238,227,159,209,\cdots,98,21,113,198,139,127,80,67\}</script>这样形成了完整的数据码字序列,然后就可以按位逐个填入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生成的全部过程。在下一篇文章也是最后一篇文章中,我们将讨论另一种数据码填充的算法“寻址法”,并结束整个系列文章

posted @ 2017-10-03 21:18  JackiePENG  阅读(7)  评论(0编辑  收藏  举报  来源