前言
小程序的菊花码识别度较高,与一般二维码相比,一眼便能辨认,只需用微信扫描即可。
在默认设置下,我们有权自行设定生成码的各项参数,包括路径、尺寸,并且可以自主选择线条颜色的自动或手动调整,同时也能决定底色是否采用透明效果。
然而,这些配置项往往是无法满足我们的定制化需求的。
以一个实例为证,我们得在确保小程序码识别度不受影响的前提下,对中间的标志进行更换,这该如何操作呢?下面,作者将亲自指导大家。
梳理思路
首先,我们需要深入分析这个问题的核心。实际上,这是一个关于图像处理的课题,且这项任务既可以在服务端完成,也可以在客户端进行。
于是就有 2 个方案:
提供端到端编码及结果整合的服务端解决方案。具体实施方法,感兴趣的朋友们,不妨阅读我的文章《Web 函数自定义镜像实战:构建图像处理函数》。
服务端生码,客户端缝合方案。这就是本篇文章具体提及的。
注:小程序码一般由服务端调用微信api接口生成
云调用——生码最简便方案
众所周知,微信小程序的构建模式分为两大类,一类是通过本地调用实现,另一类则是依托云端进行调用。
云调用作为一种针对特定场景设计的解决方案,通常能够显著提高我们的开发效率。我们计划迅速搭建一个函数,以此为我们提供必要的测试材料。
/.js:
引入云函数模块,源自“wx-server-sdk”,并初始化配置对象,具体操作如下: 在cloud.DYNAMIC_CURRENT_ENV环境变量控制下,导出一个名为main的异步函数,该函数接收两个参数:事件(event)和上下文(context)。 从事件中提取出以下参数:场景(默认为空字符串),页面,宽度(默认为430像素),自动着色,线条颜色,以及是否为半透明。 scene, page, width, autoColor, lineColor, isHyaline }) 使用打包工具后,若大家希望直接运行,只需将esm格式转换为cjs格式即可。
/.json:
{ "permissions": { "开放API接口包括:使用wxacode.getUnlimited方法获取无限二维码。" }}
通过上述 2 段代码块,我们的测试函数就部署完成了。
把返回的
const suffixMap = { 图像文件类型为JPEG,其格式标识为jpeg。 定义一个导出函数,该函数名为getPath,它接受两个参数:filename默认值为'tmp',contentType默认值为'image/jpeg'。 返回`${wx.env.USER_DATA_PATH}/${filename}.${suffixMap[contentType]||'jpeg'}`,导出`writeFile`函数,该函数接收缓冲区`buff`,内容类型`contentType`默认为`'image/jpeg'`,文件名`filename`默认为`'tmp'`。 返回一个新的Promise对象,其中包含一个执行解析的回调函数resolve,以及一个执行拒绝的回调函数reject。 创建了一个常量fsm,该常量是通过调用wx.getFileSystemManager()方法获取的文件系统管理器。 filePath变量通过调用getPath函数,并传入filename和contentType参数来获取路径。 fsm.writeFile({ filePath, data: buff, 编码格式:二进制类型, success() { resolve(filePath) }, fail(error) { reject(error) } }) })}....// 在需要用到的地方直接try { loading('生成中') console.error(e)} finally { loaded()}
客户端的图像处理
提及客户端图像处理技术,便不能不提及其原生组件,因此,我们仅需借助该组件,对小程序码中的Logo区域进行精确测量与裁剪,进而替换为我们所定制的图像。
测量
在此以标准的小程序码尺寸为基准。(为了便于理解,本案例中均采用了这种分辨率的小程序码,若需要其他分辨率,则可按比例调整进行裁剪。)
小程序码标注
根据图中的标记信息,我们可以看出在特定分辨率中,四周的边距是固定的,据此可以计算出位于中心位置的Logo圆形的直径长度为,其半径则为95像素。
所以接下来就可以轻松愉快的写代码了。
利用 2d实现
第一代小程序API已经不再适用,目前应直接采用type="2d"的版本,相关Api文档可在MDN网站上查阅。
前置标签和样式
// scss .canvas.offscreen{//采用两个class选择器,提升其优先级设置} position: absolute; bottom: 0; left: -9999rpx; // 这叫物理离屏渲染,笑~ }
核心 js 实现
初始化实例和上下文
初始化 实例和 ctx 上下文:
canvas上创建上下文对象ctx,并执行包含相应代码的onReady函数。 uni .createSelectorQuery() 若该组件包含canvas元素,则必须添加此行代码,以选择ID为'#canvas'的元素。 .fields({ node: true, size: true }) .exec((res) => { if (res[0]) { 此画布变量被赋值为传入的canvas,同时与资源数组res的第一个元素的node属性相等。 根据设备的pixelRatio进行相应比例的调整,但在此处为了演示的便捷性,我们直接将canvas的宽度设置为430。 canvas.height = 430 } })}
第一次渲染-画布背景
第一次渲染,把小程序码,作为图像传入画布。 :
同步执行绘制背景函数,参数为原始二维码链接。 此处可设定为远程地址(需设置downloadUrl),亦或是本地地址(可返回参数自行处理),甚至包括以cloud://为前缀的云存储链接,src字段则对应orginQrcodeUrl。 }) if (err) { throw err } const { path } = res canvas上创建了一个新的图像实例,并将其赋值给变量img。 img.src = path 创建一个新的Promise对象,并在其中定义一个回调函数,该函数包含两个参数:resolve和reject,用于处理异步操作的完成或失败。 img.onload = () => { 将小程序码完整地绘制至画布之上,具体操作如下:将图片img按照其原始尺寸,从画布的左上角(坐标0,0)开始,填充至整个画布的宽度(canvas.width)和高度(canvas.height)。 resolve() } 当图片加载失败时,设置其错误处理函数为箭头函数,该函数接受一个事件对象作为参数。 reject(event) } })},
第二次渲染-裁剪加填充
第二次渲染,把背景裁剪一个圆,并把图片填充进去。 :
异步绘制头像函数,接受远程头像URL作为参数,进行绘制操作。 const [err, res] = await uni.getImageInfo({ 我采用了云存储服务中的图片链接格式,具体为:prefix为cloud://,src字段则指向远程头像的URL。 }) if (err) { throw err } const { path } = res const img = canvas.createImage() img.src = path 测量数据被应用于此处,设定了以下变量:x轴偏移量为120px,y轴偏移量同样为120px;圆的直径为190px,通过计算得到半径为95px;为了去除原有logo的纯色边缘,增加了2px的边框宽度;接下来定义了一个对象,用于描述裁剪圆的大小属性,其中x坐标为120px偏移加上半径。 y: offsetY + radius, 半径:半径数值增加边界宽度值 } await new Promise((resolve, reject) => { img.onload = () => { ctx.save() 启动操作!将原有的标志图案彻底移除!执行绘制圆形命令,设置圆心坐标为circle.x和circle.y,半径为circle.radius,绘制从0度到180度的半圆,逆时针方向。 ctx.clip() 将我们所需的自定义图片,均匀地铺展开来,其中!使用ctx.drawImage()函数即可实现。 img, X轴偏移量减去边框宽度, 偏移量减去边框宽度。 circle.radius * 2, circle.radius * 2 ) 执行完毕后,调用ctx对象的restore方法以恢复之前保存的画布状态。 resolve() } img.onerror = (event) => { reject(event) } })},
经过上述几个步骤,我们便能轻松地完成图像处理环节,进而将中间预设的Logo替换为个性化的图像。
预览及下载到本地
异步获取tempFilePath,并调用getImage函数。 在执行uni.canvasToTempFilePath函数时,我们期待着得到两个结果:一个错误信息和一个响应对象,分别用const关键字声明的err和res变量来接收,这个过程是通过await关键字来异步等待的。 canvas }) if (err) { throw err } 返回结果临时文件路径,执行预览操作,通过async函数preview传入源文件src。 if (src) { uni.previewImage({ urls: [src] }) 将图片资料存入相册中,通过异步操作实现,调用save函数并传入图片源路径。 try { 先进行授权操作,然后执行保存步骤,等待`authorize('scope.writePhotosAlbum')`函数的执行结果。 在执行uni.saveImageToPhotosAlbum()函数时,我们得到了两个结果,一个是错误信息(err),另一个是响应数据(res),整个过程通过await关键字进行异步等待。 filePath: src }) if (err) { throw err } 此操作已顺利完成,系统提示“保存成功!”。 } catch (e) { console.error(e) }}
就这样,客户端生成自定义小程序码的整套解决方案就完成了
效果展示
自定义码
您可以通过微信搜索“程序员名片”,随后对名片进行维护,上传个人头像,并点击下方的“分享二维码”按钮,这样就可以提前查看效果了。