javaScript 二维码生成实战

·  阅读 2655

背景

因项目开发经常会需要使用到二维码,无论是使用React或Vue框架都会找一找相关的封装好的框架,看着都是基于qr.js或者qrcode.js套一个外壳,还有些其他的版本的实现,看了看和qr.js大同小异,甚至可以说都是直接基于qr.js添加些功能。看着这个项目都是8年前的项目了,说明二维码的生成算法稳定的同时也是反应出其比较复杂,以至于都没有人重新造轮子?

由于经常用到二维码,不了解了解原理,总感觉缺失点乐趣。尝试找过些文章:

  1. 二维码的生成细节和原理
  2. 深度探索二维码原理及其应用
  3. 二维码生成原理及解析代码
  4. qrcode tutorial

感觉都是照着规范再介绍,博客文章内容也差不多,反正看完不仅没有看懂,还被劝退了几次。本着不能认怂的态度,这周决定还是从看源码 + 看博客 => 手撸简易版二维码实现。这个过程整整花了 1 天左右,撸完代码后,对二维码的一些概念还是不太理解,尤其是错误码相关的,涉及应该很复杂的数学知识,纠结甚久,还是放弃。。 文章涉及代码可以看 简易版版二维码实现

实战准备

选择源码

用过好几个版本的二维码,例如qrcode-terminalqrcode.react,qrcode.js等,从github上看其对应的qrcode实现,qrcode-terminal里面包含qrcode实现; qrcode.react引用了qr.js; qrcode.js也直接就包含qrcode的实现,但是压缩版本。对比后,选择了阅读qr.js,选择它的原因,看起来更像是是原创的,而且结构也清晰些。

理论准备

实战之前,还是推荐看 二维码生成原理及解析代码,直接上手源码会迷失在代码细节中,对照着这个文档先简单看看源码。至少这个图得先了解清楚:

环境准备

手撸代码前,因为是本地开发,为了方便预览、调试,期望本地可以直接在 terminal 中展示二维码,故直接先"借鉴" qrcode-terminal的做法,先完成了一个terminal预览二维码的工具,具体可以参考 Terminal展示二维码

手撸代码

手撸代码实现了一个生成 hello world 的二维码,先上二维码:

代码结构

先大致看了一遍源码后,结合文档二维码生成原理及解析代码中生成二维码过程,思路就根据如下图片进行: 因为设想是完成一个简单版本的二维码生成器,为了方便,所有代码都放在一个文件中,代码结构为:

具体代码实现可以看简易版版二维码实现

1.初始化

因为二维码有较多版本,不同版本的尺寸不一样,本想实现 version1 版本,但过于简单,连个对齐图案都没有,故就实现了 version2版本,使用的字节编码(Byte Mode)模式,不同模式生成二维码对应的二进制位数不一样。在初始化时就设定好版本,同时创建一个modules属性存储二维码图案信息,使用 1 标识黑块,0 标识白块,null标识还未被处理,吐血的经验,图案信息需要三个状态,一开始实现我都初始化为 0,后面处理数据码和纠错码时,死活会有问题。

2.定位图案的实现

定位图案是分别在左上、右上、左下三个角的方块,有固定的尺寸大小,如下图:

在实现时,千万不能忘记了定位图片需要被白色块包裹,也就是理论准备中二维图中的分割器(Separators fro Position Detection Patterns),此处又踩了个坑。实现方式也相对源码取巧,生成一个二维数组标识方块,如下图,然后遍历该二维数组,设置对应modules属性中位置:

同理,对齐图案因为也是一个固定大小的方块,实现方式也类似,就不赘述了,具体可以看源码。 效果图为:

3. 时序图案实现

时序图案就是定位图片三个角连成的两条线,应该是里面最简单的一个:

对应效果为:

4. 版本&格式信息

版本和格式信息一共 15 bits,包含 5bits 的数据位 + 10bits 纠错位。其中数据位为 2bits 纠错等级 + 3bits蒙版。一共会有两个位置会存储,如下图的左边那个。涉及到纠错位的生成,开始需要用到些数学知识,我也不太懂啊,照着 qr.js中的实现改造了些,代码就直接看源码吧,上个效果图:

5. 数据码&纠错码

最难的地方了,看了最长的时间,里面涉及到二进制的操作,和常规运算不太一样。主要难点有两个:

  1. 头部为4bit的编码模式,其次是8bit的数据大小,排列后还需要根据8bit生成数据码,源码不好理解,用常规思路改造了一下。
  2. 纠错码会涉及到多项式的计算,恕我太菜,这块真的懵逼了,直接复制qr.js源码的中实现。

使用下述函数生成对应位数的二进制编码

function getBinary(num, length) {
  const long = '00000000000000000000000000000000' // 长度没啥具体含义,保证比 length大就好
  const binary = num.toString(2)
  if (binary.length < length) {
    return long.slice(0, length - binary.length) + binary
  }
  return binary
}
复制代码

具体使用如下: 后面的纠错码也是懵逼状态,涉及到多项式,蒙版图案,可能是文案太简单,试了试不同蒙版(对应源码中的maskPattern字段,可0-7共8中模式),也都差不多,直接上效果图吧:

总结

手撸二维码实现,虽然实际没有多大意义,但心理上还是有些喜悦,破除一个魔障。。虽然涉及到较多理论知识,但看不懂的就过,能用自己方式实现的就用自己的方式实现,无所谓好与坏,用自己方式实现,拓宽自己的思路,同时也是加深理解的一种方式。

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改