canvas 绘制带logo/文字二维码

本来我是负责微信小程序的,后来看到项目web端生成的二维码比较单调,就决定在二维码中间加一个 logo,于是就在网上到处找现成的源码,后来终于在github上找到了一份可以在二维码中间放logo的qrcode 库。结果后来boss提了需求,想在中间显示文字,而不是图片,这下尴尬了,这个需求有点奇葩,怕是找不 到源码了,于是无奈之下我决定深入这个二维码生成库的内部,去了解一下这个东东究竟是怎么画出来的。 首先是研究一下这个logo是如何画出来的。由于我本身没有太多web开发经验,尤其没有研究过 jquery,所以对于这个库的架构还是很蒙蔽的,但是多少还是在微信小程序开发中用了js,所以哪怕框架结 构看的不是很清晰,但每个函数的作用还是可以看懂的。根据关键字imgWidth定位到这样一个函数:

var QRMode, QRErrorCorrectLevel, QRMaskPattern, QRUtil, QRMath, i;
for (function (a) {
        a.fn.qrcode = function (b) {
            var c, d;
            return "string" == typeof b && (b = {
                text: b
            }), b = a.extend({}, {
                width: 256,
                height: 256,
                cheight: b.height + 50,
                cwidth: b.width,
                cstyle:"width:"+b.cwidth/4+"px;height:"+b.cheight/4+"px;",
                content:"",
                imgWidth: b.width / 2.5,
                imgHeight: b.height / 2.5,
                typeNumber: -1,
                correctLevel: QRErrorCorrectLevel.H,
                background: "#ffffff",
                foreground: "#000000"

说实话,我对这样的结构真的好无头绪,看起来好像是匿名函数,但是直接一个for是个什么鬼?这代码是我根据压缩代码直接恢复的,只能解释为可能这是个完整的for语句,只不过for里放了太多东西导致后面的逻辑在很远的地方。但是先不管这些,我的目的是查找图片绘制的逻辑,继续往下看:

c = function () {
                var c, d, e, f, g, h, i, j, k, a = new QRCode(b.typeNumber, b.correctLevel);
                for (a.addData(b.text), a.make(), c = document.createElement("canvas"), c.width = b.cwidth, c.height = b.cheight,c.style=b.cstyle, d = c.getContext("2d"), b.src && (e = new Image(), e.src = b.src, e.onload = function () {
                        d.drawImage(e, (b.width - b.imgWidth) / 2, (b.height - b.imgHeight) / 2, b.imgWidth, b.imgHeight)
                    }), f = b.width / a.getModuleCount(), g = b.height / a.getModuleCount(), h = 0; h < a.getModuleCount(); h++) {
                    for (i = 0; i < a.getModuleCount(); i++) {
                        d.fillStyle = a.isDark(h, i) ? b.foreground : b.background,
                            j = Math.ceil((i + 1) * f) - Math.floor(i * f),
                            k = Math.ceil((h + 1) * f) - Math.floor(h * f),
                            d.fillRect(Math.round(i * f), Math.round(h * g), j, k)
                    }
                }
                return c
            }

在下面发现这样一个名叫c的函数,不得不说,压缩的代码命名方式果然简单暴力,全是单个字母的。但是幸好这个函数我是读得懂的。首先是生成一个QRCode的对象,并且用解构赋值的方式将返回值保存在c, d, e, f, g, h, i, j, k, a几个变量中,具体干嘛的我就先不管,专心往下看图片的部分。 接下来的一行是一个for循环语句,但是在for内部的第一部分进行了大量的赋值操作以及初始化操作,甚至生成了一个Image的对象保存在了e变量中,并且对e的图片资源进行了赋值以及绘制图片的操作也都完成了:

e = new Image(), e.src = b.src, e.onload = function () {
        d.drawImage(e, (b.width - b.imgWidth) / 2, (b.height - b.imgHeight) / 2,             
        b.imgWidth, b.imgHeight)
}

看到这里部分不惊叹压缩过的代码的精悍,短短一个for语句居然可以做这么多事。其中这里有一个d变量, 这个d就是获取的canvas的contex,这部分代码也是在这个for语句第一部分里:

d = c.getContext("2d")

于是就这么神奇的完成了对logo的绘制。但是既然看到了这里,我不如直接看看二维码本身是如何绘制的好 了,于是继续往下读:

for (i = 0; i < a.getModuleCount(); i++) {
        d.fillStyle = a.isDark(h, i) ? b.foreground : b.background,
         j = Math.ceil((i + 1) * f) - Math.floor(i * f),
         k = Math.ceil((h + 1) * f) - Math.floor(h * f),
         d.fillRect(Math.round(i * f), Math.round(h * g), j, k);
}

这就是之前绘制图片那个for循环内部的逻辑,又是一个循环,但是这个循环就正常多了,没有那么花哨的组合,可以看到这里用到了上一层循环中初始化的 f,g三个变量,这里分别是计算出来的需要绘制的二维码的每一个小块的宽度和高度,整体的两个for循环组合起来,类似于我们的打字机一样,一行一行的扫描保存了二维码信息的数组a,并一个小块一个小块的绘制在canvas上,至于这个a是如何计算的,这就属于二维码计算的逻辑了,就算我懂了也改不了什么,就不看了。 这里是logo绘制的展示图:

带logo的二维码

示例代码

现在弄懂了这个logo绘制的逻辑,于是开始把这个image绘制改为文字绘制。 首先删掉外层for循环中图片绘制部分:

for (a.addData(b.text), 
      a.make(), 
      c = document.createElement("canvas"),
      c.width = b.cwidth, 
      c.height = b.cheight, 
      c.style = b.cstyle, 
      d = c.getContext("2d"),
      d.fillStyle = b.background, 
      d.fillRect(0, 0, b.cwidth, b.cheight),
      f = b.width / a.getModuleCount(), 
      g = b.height / a.getModuleCount(), 
      h = 0; h < a.getModuleCount(); h++) {
      for (i = 0; i < a.getModuleCount(); i++) {
               d.fillStyle = a.isDark(h, i) ? b.foreground : b.background,
                j = Math.ceil((i + 1) * f) - Math.floor(i * f),
                k = Math.ceil((h + 1) * f) - Math.floor(h * f),
                d.fillRect(Math.round(i * f + b.border), Math.round(h * g + b.border), j, k)
      }
}

然后在canvas中绘制一个白色的不透明圆形作为文字的背景,当然如果你喜欢绿色,也是可以的。

                d.beginPath();
                d.strokeStyle = "white";
                d.arc(b.width / 2 + b.border, b.height / 2 + b.border, 35, 0, Math.PI * 2, false);
                d.closePath();
                d.fillStyle = 'white';
                d.fill();

在外部传参部分添加了用于接收需要传入文字的变量type,这样在调用qrcode的时候就可以通过修改传入的type字段控制二维码显示的文字信息,绘制中间文字的代码如下:

                d.font = "normal 35px arial";
                //设置字体颜色
                d.fillStyle = "red";
                d.moveTo(d.width / 2 + b.border, 20);
                d.lineTo(d.width / 2 + b.border, 50);
                d.stroke();
                d.textAlign = "center"
                d.fillText(b.type, Math.floor(b.width / 2 + b.border), Math.floor(b.height / 2 +b.border +12));

文字的显示我使用的是红色,没有特殊原因,骚气。 除此之外我在二维码周边也留下了边框,这样的话显示二维码的时候就不会与周围的空间显得太拥挤,尤其是通过绘制在canvas内部的边框,可以使得下载下来的二维码更美观,最终结果显示如下:

显示文字的二维码

示例代码

猜你喜欢

转载自my.oschina.net/u/2276921/blog/1574522