基于Excel的QR二维码生成工具——原理及算法详解(之七)
在上一篇文章中,我们讨论了一个自定义二维码填充函数,这个函数返回一个Byte型二位数组,数组的内容正是二维码矩阵的所有功能图形和格式版本信息,其中深色单元模块填充数字为1,浅色为0.最后,这个数组中还有大量的单元模块填充了数字3,这些模块就是为了数据填充做准备的。从本节开始,我们将要讨论二维码填充的最后一个部分:数据码字的填充。
数据码字除了包含我们已经完成编码的数据之外,还包含根据里德所罗门算法计算出来的数据纠错码,两部分按照一定的规律连接起来,就成为需要填充的数据码字。对于每个不同的版本,QR规范中都规定了不同的码字连接排列规则。对大部分版本的二维码来说,数据都是打乱顺序排列的,下面以版本“”为例,简单介绍数据码字的乱序排列方式。
我们要将下面的文字编码为一个6-H级别的二维码:
“月落乌啼霜满天,江枫渔火对愁眠
姑苏城外寒山寺,夜半钟声到客船”
首先将文字编码,采用中文汉字编码模式,编码后的数据码字为:
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> |
---|---|---|---|---|---|---|---|---|---|---|
209 | 15 | 139 | ⋯ <script type="math/tex" id="MathJax-Element-207">\cdots</script> | 203 | 99 | 55 | 238 | ⋯ <script type="math/tex" id="MathJax-Element-208">\cdots</script> | 98 | 139 |
69 | 97 | 168 | ⋯ <script type="math/tex" id="MathJax-Element-209">\cdots</script> | 162 | 63 | 45 | 227 | ⋯ <script type="math/tex" id="MathJax-Element-210">\cdots</script> | 21 | 127 |
30 | 116 | 39 | ⋯ <script type="math/tex" id="MathJax-Element-211">\cdots</script> | 9 | 81 | 48 | 159 | ⋯ <script type="math/tex" id="MathJax-Element-212">\cdots</script> | 113 | 80 |
130 | 5 | 72 | ⋯ <script type="math/tex" id="MathJax-Element-213">\cdots</script> | 17 | 176 | 209 | 48 | ⋯ <script type="math/tex" id="MathJax-Element-214">\cdots</script> | 198 | 67 |
接下来,将上面的数据码字和纠错码字排列好后,从左上角开始从上到下,再从左到右逐列读取所有的数据,并重新排列:
数据码字的填充规则是这样的:首先从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生成的全部过程。在下一篇文章也是最后一篇文章中,我们将讨论另一种数据码填充的算法“寻址法”,并结束整个系列文章
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战