Loading... 前段时间在将网页版[三合一收款码](https://github.com/BWmelon/qrcode)转移到[小程序版](https://github.com/BWmelon/qrcode-weapp)的过程中遇到了一个问题,就是原来的艺术码生成库只能在网页使用,小程序中无法使用。后来在Github中找了一段时间,然而并没有找到相关的库,之前生成库的作者也有一段时间没在Github上活跃了,也就没厚着脸皮找他将生成库兼容到小程序,便只能自己硬着头皮封装一个小程序版的艺术二维码生成库。 ## 1、定位点绘制 实现艺术二维码的核心是如何将素材按指定要求渲染到背景图上去。二维码的具体生成规则还没有理解,但是从通过小程序二维码生成库[weapp-qrcode](https://github.com/yingye/weapp-qrcode)生成的二维码中可以发现,如果将二维码进行平均划分,左上角、右上角和左下角的三个定位点宽高都占据了七份,这个值是在经过修改二维码纠错级别 `correctLevel`后无论如何也不会发生改变的。 这样,我们便可计算出三个定位点的宽高和位置:以二维码的宽(width)、高(height)、横向点阵数量(num)为例,那么单个点阵占据的宽高为 `pWidth = width / num`,定位点的宽高为 `(width / num) * 7`,左上角定位点的起始绘制位置为 `(0, 0)`,右上角的起始绘制位置为 `((num - 1) - 7) * pWidth, 0)`,左下角的起始绘制位置为 `(0, (num - 1) - 7) * pWidth)`。  ## 2、信息点绘制 二维码的信息点可以根据自己的需要定制,一般来说占用面积越小,出现的概率越大。这里我预设了如下图所示可以使用的信息点:  我们可以将二维码转换为一个二维数组,有数据的位置标志为1,没有数据的位置标记为0,那么一个二维码可以表示为: ```js [ [1, 1, 1, 1, 1, 1, 0, 0, 1, 0, ....], [0, 1, 0, 1, 1, 1, 0, 0, 1, 0, ....], [0, 1, 0, 1, 1, 1, 0, 0, 1, 0, ....], [0, 1, 0, 1, 1, 1, 0, 0, 1, 0, ....], [0, 1, 0, 1, 1, 1, 0, 0, 1, 0, ....], [0, 1, 0, 1, 1, 1, 0, 0, 1, 0, ....], [0, 1, 0, 1, 1, 1, 0, 0, 1, 0, ....], [0, 1, 0, 1, 1, 1, 0, 0, 1, 0, ....], [0, 1, 0, 1, 1, 1, 0, 0, 1, 0, ....], [0, 1, 0, 1, 1, 1, 0, 0, 1, 0, ....], [0, 1, 0, 1, 1, 1, 0, 0, 1, 0, ....], ... ] ``` 除去三个定位点,我们可以通过遍历是否存在可以放置指定比例素材的位置,如果存在,将素材和素材的位置进行记录。我们以row2col2为例,设第一个信息点的位置为 `(x, y)`,这时去循环判断 `(x, y)、(x + 1, y)、(x, y + 1)、(x + 1, y + 1)`这四个位置的标志是否都为1,如果满足条件,则将这四个位置标记为已使用,并将起始位置 `(x, y)`添加到row2col2需要渲染的位置中。 将所有的能够渲染的素材遍历完成之后,我们会得到素材对应的坐标位置,然后将素材进行渲染即可。下面为实现艺术二维码生成的部分代码: ```js function drawQrcode(options) { options = options || {}; options = extend(true, { width: 256, height: 256, x: 0, y: 0, typeNumber: -1, correctLevel: QRErrorCorrectLevel.H, background: '#ffffff', foreground: '#000000', image: { imageResource: '', dx: 0, dy: 0, dWidth: 256, dHeight: 256 }, materials: { eye: [], col4: [], row4: [], row2col3: [], row3col2: [], col3: [], row3: [], row2col2: [], col2: [], row2: [], single: [] } }, options); if (!options.canvasId && !options.ctx) { console.warn('please set canvasId or ctx!'); return; } createCanvas(); function createCanvas() { // create the qrcode itself var qrcode = new QRCode(options.typeNumber, options.correctLevel); qrcode.addData(utf16to8(options.text)); qrcode.make(); // get canvas context var ctx; if (options.ctx) { ctx = options.ctx; } else { ctx = options._this ? wx.createCanvasContext && wx.createCanvasContext(options.canvasId, options._this) : wx.createCanvasContext && wx.createCanvasContext(options.canvasId); } // count dots let dotArray = [] for (var row = 0; row < qrcode.getModuleCount(); row++) { let arr = [] for (var col = 0; col < qrcode.getModuleCount(); col++) { arr.push(qrcode.isDark(row, col) ? 1 : 0) } dotArray.push(arr) } let descPosition = { eye: [], col4: [], row4: [], row2col3: [], row3col2: [], col3: [], row3: [], row2col2: [], col2: [], row2: [], single: [] } let copyDotArray = dotArray.map(item => item.map(iitem => false)) function isMatchRule(rowIndex, colIndex) { return copyDotArray[rowIndex] && copyDotArray[rowIndex][colIndex] === false && dotArray[rowIndex][colIndex] === 1 } // position dot dotArray.forEach((row, rowIndex) => { row.forEach((col, colIndex) => { if ((rowIndex < 7 && colIndex < 7) || (rowIndex < 7 && colIndex > row.length - 1 - 7) || (rowIndex > row.length - 1 - 7 && colIndex < 7)) { copyDotArray[rowIndex][colIndex] = true if ((rowIndex === 0 && colIndex === 0) || (rowIndex === 0 && colIndex === row.length - 7) || (rowIndex === row.length - 7 && colIndex === 0)) { descPosition.eye.push([rowIndex, colIndex]) } } }) }) // not position dot dotArray.forEach((row, rowIndex) => { row.forEach((col, colIndex) => { if ((rowIndex < 7 && colIndex < 7) || (rowIndex < 7 && colIndex > row.length - 1 - 7) || (rowIndex > row.length - 1 - 7 && colIndex < 7)) { } else { // col4 if (options.materials.col4.length && isMatchRule(rowIndex, colIndex) && isMatchRule(rowIndex, colIndex + 1) && isMatchRule(rowIndex, colIndex + 2) && isMatchRule(rowIndex, colIndex + 3)) { copyDotArray[rowIndex][colIndex] = copyDotArray[rowIndex][colIndex + 1] = copyDotArray[rowIndex][colIndex + 2] = copyDotArray[rowIndex][colIndex + 3] = true descPosition.col4.push([rowIndex, colIndex]) } // row4 if (options.materials.row4.length && isMatchRule(rowIndex, colIndex) && isMatchRule(rowIndex + 1, colIndex) && isMatchRule(rowIndex + 2, colIndex) && isMatchRule(rowIndex + 3, colIndex)) { copyDotArray[rowIndex][colIndex] = copyDotArray[rowIndex + 1][colIndex] = copyDotArray[rowIndex + 2][colIndex] = copyDotArray[rowIndex + 3][colIndex] = true descPosition.row4.push([rowIndex, colIndex]) } // row2col3 if (options.materials.row2col3.length && isMatchRule(rowIndex, colIndex) && isMatchRule(rowIndex, colIndex + 1) && isMatchRule(rowIndex, colIndex + 2) && isMatchRule(rowIndex + 1, colIndex) && isMatchRule(rowIndex + 1, colIndex + 1) && isMatchRule(rowIndex + 1, colIndex + 2)) { copyDotArray[rowIndex][colIndex] = copyDotArray[rowIndex][colIndex + 1] = copyDotArray[rowIndex][colIndex + 2] = copyDotArray[rowIndex + 1][colIndex] = copyDotArray[rowIndex + 1][colIndex + 1] = copyDotArray[rowIndex + 1][colIndex + 2] = true descPosition.row2col3.push([rowIndex, colIndex]) } // row3col2 if (options.materials.row3col2.length && isMatchRule(rowIndex, colIndex) && isMatchRule(rowIndex, colIndex + 1) && isMatchRule(rowIndex + 1, colIndex) && isMatchRule(rowIndex + 1, colIndex + 1) && isMatchRule(rowIndex + 2, colIndex) && isMatchRule(rowIndex + 2, colIndex + 1)) { copyDotArray[rowIndex][colIndex] = copyDotArray[rowIndex][colIndex + 1] = copyDotArray[rowIndex + 1][colIndex] = copyDotArray[rowIndex + 1][colIndex + 1] = copyDotArray[rowIndex + 2][colIndex] = copyDotArray[rowIndex + 2][colIndex + 1] = true descPosition.row3col2.push([rowIndex, colIndex]) } // col3 if (options.materials.col3.length && isMatchRule(rowIndex, colIndex) && isMatchRule(rowIndex, colIndex + 1) && isMatchRule(rowIndex, colIndex + 2)) { copyDotArray[rowIndex][colIndex] = copyDotArray[rowIndex][colIndex + 1] = copyDotArray[rowIndex][colIndex + 2] = true descPosition.col3.push([rowIndex, colIndex]) } // row3 if (options.materials.row3.length && isMatchRule(rowIndex, colIndex) && isMatchRule(rowIndex + 1, colIndex) && isMatchRule(rowIndex + 2, colIndex)) { copyDotArray[rowIndex][colIndex] = copyDotArray[rowIndex + 1][colIndex] = copyDotArray[rowIndex + 2][colIndex] = true descPosition.row3.push([rowIndex, colIndex]) } // row2col2 if (options.materials.row2col2.length && isMatchRule(rowIndex, colIndex) && isMatchRule(rowIndex, colIndex + 1) && isMatchRule(rowIndex + 1, colIndex) && isMatchRule(rowIndex + 1, colIndex + 1)) { copyDotArray[rowIndex][colIndex] = copyDotArray[rowIndex][colIndex + 1] = copyDotArray[rowIndex + 1][colIndex] = copyDotArray[rowIndex + 1][colIndex + 1] = true descPosition.row2col2.push([rowIndex, colIndex]) } // col2 if (options.materials.col2.length && isMatchRule(rowIndex, colIndex) && isMatchRule(rowIndex, colIndex + 1)) { copyDotArray[rowIndex][colIndex] = copyDotArray[rowIndex][colIndex + 1] = true descPosition.col2.push([rowIndex, colIndex]) } // row2 if (options.materials.row2.length && isMatchRule(rowIndex, colIndex) && isMatchRule(rowIndex + 1, colIndex)) { copyDotArray[rowIndex][colIndex] = copyDotArray[rowIndex + 1][colIndex] = true descPosition.row2.push([rowIndex, colIndex]) } // single if (options.materials.single.length && isMatchRule(rowIndex, colIndex)) { copyDotArray[rowIndex][colIndex] = true descPosition.single.push([rowIndex, colIndex]) } } }) }) if (options.image.imageResource) { ctx.drawImage(options.image.imageResource, options.image.dx, options.image.dy, options.image.dWidth, options.image.dHeight); } // compute tileW/tileH based on options.width/options.height var tileW = options.width / qrcode.getModuleCount(); var tileH = options.height / qrcode.getModuleCount(); // draw materials function drawMaterials(type, colNum, rowNum) { descPosition[type].forEach((item, index) => { ctx.drawImage(options.materials[type][Math.floor((Math.random() * options.materials[type].length))], options.x + item[1] * tileW, options.y + item[0] * tileH, tileW * colNum, tileH * rowNum) }) } drawMaterials('eye', 7, 7) drawMaterials('col4', 4, 1) drawMaterials('row4', 1, 4) drawMaterials('row2col3', 2, 3) drawMaterials('row3col2', 3, 2) drawMaterials('col3', 3, 1) drawMaterials('row3', 1, 3) drawMaterials('row2col2', 2, 2) drawMaterials('col2', 2, 1) drawMaterials('row2', 1, 2) drawMaterials('single', 1, 1) // callback ctx.draw(false, function(e) { options.callback && options.callback(e); }); } } ``` ## 3、添加背景图 只绘制一个艺术二维码有时候会比较单调,这时可以为艺术二维码添加背景图,然后将二维码按一定比例在背景图上。需要注意的是, 背景图的绘制需要在二维码之前。 ## 4、使用方法 最终的源码我放在了github: > [https://github.com/BWmelon/artQrcode](https://github.com/BWmelon/artQrcode) 在页面中添加一个 `canvas`标签和 `image`标签,代码如下: ```html <image src="{{url}}" class="image" mode="widthFix" bindtap="handlePreview" style="width: 750rpx;"></image> <canvas style="width: 900px; height: 1200px;position: absolute;left: -99999rpx;" canvas-id="myQrcodeOne"></canvas> ``` 在js中引入生成库: ```js import drawQrcode from '../../utils/weapp.artQrcode.js' ``` 生成艺术二维码 ```js drawQrcode({ width: 520, height: 520, canvasId: 'myQrcodeOne', // ctx: wx.createCanvasContext('myQrcodeOne'), text: 'https://qr.no0a.cn', x: 180, y: 100, // v1.0.0+版本支持在二维码上绘制图片 image: { imageResource: '../../images/materials/xiaohuangren/border.png', dx: 0, dy: 0, dWidth: 900, dHeight: 1200 }, materials: { eye: ["../../images/materials/xiaohuangren/eye.png"], row3: ["../../images/materials/xiaohuangren/row3.png"], row2col3: ["../../images/materials/xiaohuangren/row2col3.png"], row2col2: ["../../images/materials/xiaohuangren/row2col2.png", "../../images/materials/xiaohuangren/row2col2_2.png"], row2: ["../../images/materials/xiaohuangren/row2.png", "../../images/materials/xiaohuangren/row2_2.png"], single: ["../../images/materials/xiaohuangren/single.png"], }, callback: () => { wx.canvasToTempFilePath({ canvasId: 'myQrcodeOne', success: (res) => { console.log(res.tempFilePath) this.setData({ url: res.tempFilePath }) } }) } }) ``` ## 5、参数说明 | 参数 | 说明 | 类型 | 示例 | | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | width | 必须,二维码宽度 | Number | 520 | | height | 必须,二维码高度 | Number | 520 | | text | 必须,二维码内容 | String | [https://github.com/BWmelon/artQrcode](https://github.com/BWmelon/artQrcode) | | canvasId | 必须,绘制的`canvasId` | String | `'myQrcode'` | | x | 非必须,二维码x轴相对于背景图起始位置,默认为0 | Number | 100 | | y | 非必须,二维码y轴相对于背景图起始位置,默认为0 | Number | 100 | | correctLevel | 非必须,二维码纠错级别,默认值为高级,取值:`{ L: 1, M: 0, Q: 3, H: 2 }` | Number | 1 | | _this | 非必须,若在组件中使用,需要传入 | | this | | callback | 非必须,绘制完成后的回调函数 | Function | () => {console.log('完成')} | | image | 必须,背景图 image.imageResource:背景图位置 image.dx:背景图起始x轴 image.dy:背景图起始y轴 image.dWidth:背景图绘制宽度 image.dHeight:背景图绘制高度 | Object | { imageRosourse: '../../images/materials/border.png', dx: 0, dy: 0, dWidth: 900, dHeight: 1200 } | | materials | 必须,素材 可选值:eye、col4、row4、row2col3、row3col2、col3、row3、row2col2、col2、row2、single 每一项的值都是一个数组,如果该项素材数量大于1,将会进行随机渲染 没有素材的选项可以不填或者为空数组 | Object | { eye: ["../../images/materials/xiaohuangren/eye.png"], row3:["../../images/materials/xiaohuangren/row3.png"], row2col3: ["../../images/materials/xiaohuangren/row2col3.png"], row2col2: ["../../images/materials/xiaohuangren/row2col2.png", "../../images/materials/xiaohuangren/row2col2_2.png"], row2: ["../../images/materials/xiaohuangren/row2.png", "../../images/materials/xiaohuangren/row2_2.png"], single: ["../../images/materials/xiaohuangren/single.png"] } | ## 6、效果  ## 7、感谢 [252860883/ArtQRCode](https://github.com/252860883/ArtQRCode) [yingye/weapp-qrcode](https://github.com/yingye/weapp-qrcode) 最后修改:2021 年 03 月 07 日 05 : 07 PM © 允许规范转载 赞赏 如果觉得我的文章对你有用,请随意赞赏 ×Close 赞赏作者 扫一扫支付 支付宝支付 微信支付
1 条评论
顶礼膜拜 ∠( ᐛ 」∠)_