我们经常会在微信上生成一些个性化的海报,海报中嵌入用户的微信头像和昵称,以及应用的二维码,如下两张海报:


下面先与大家分享如何在后台使用nodejs生成海报。
ps:在后台生成海报有好处也有坏处。相对于前端生成,好处是:由于服务器的配置统一,生成出的海报的尺寸一致,不会有变形的情况;坏处是:由于后台生成需要引入文字包,一般来说普遍不支持emoji表情符号。
准备工作
先安装gm和sharp这两个图片处理包。
gm和sharp在linux上安装会有权限问题,需要手动创建文件夹。
或者使用如下命令安装:
npm install --unsafe-perm
安装部署方式参考之前的博客
https://www.daguanren.cc/post/promise_graphicsmagick.html
gm的下载地址:
https://sourceforge.net/projects/graphicsmagick/files/graphicsmagick/
实现方式
以第一张海报图为例,我们讲解代码如何实现。
思路如下:
- 先来直观的看下最终实现的效果图和所需的背景图,就可以大致知道我们要在哪些区域写入文字和拼接图片


- 需要在背景图上拼接一个头像和一个二维码,以及写入昵称(姓名)、手机号、过期日期这些白色文字。由于昵称的长度不固定,所以如果采用上下排布的方式需要对昵称进行居中,比较麻烦,所以稍作调整,采用左右排布的方式。最终效果如本文第一张海报所示
- 接下来各个击破,微信头像可以用通过微信公众号或小程序的api获取,例如公众号的获取用户基本信息
- 获取的头像通常为一个远程的url,例如http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0 我们需要将远程头像下载到本地临时文件夹,以便后续使用sharp和gm进行拼接
- 带参二维码的获取方式也类似,参照生成带参数的二维码api。生成后通过ticket换取二维码的url
// 引入用到的npm包,没安装的先npm install 安装
const rp =require('request-promise');
const gm = require('gm');
const sharp = require('sharp');
const fs = require('fs');
// 定义请求参数
const options = {
method: 'GET',
uri: img_url, // 头像或二维码地址
encoding: 'binary' // 或者也可以是 null
}
// 发送请求
let res = await rp(options);
// 生成一串字符串来标记该头像是谁的
let variable = think.uuid();
// 将图片存到本地临时文件,存在了根目录下的tmp/img/文件夹下
await fs.writeFileSync('tmp/img/avatar_'+variable +'.png', res, 'binary');
// 头像和二维码均使用此方式存储到本地
// 省略.....
// 头像存储好了后,由于头像是正方形的,我们要将其改成圆形,并且调整其大小
// 使用sharp将头像转成圆角
const roundedCorners = Buffer.from(
'<svg><rect x="0" y="0" width="130" height="130" rx="65" ry="65"/></svg>'
)
let sharpStream2 = sharp('tmp/img/avatar_'+ variable +'.png')
.resize(130,130)
.composite([{input:roundedCorners, blend: 'dest-in'}])
.png()
// 将异步的sharpStream2.toFile函数封装下,改成同步
let sharpWrite2 = think.promisify(sharpStream2.toFile, sharpStream2);
// 将圆形头像存储到临时文件夹
await sharpWrite2('tmp/img/circleAvatar_'+ variable +'.png');
// 同理,可以对二维码做类似处理
// 缩小二维码
let sharpStream = sharp(qrcode_local_path)
.resize(193,193)
.png()
let sharpWrite1 = think.promisify(sharpStream.toFile, sharpStream);
await sharpWrite1(qrcode_local_path_output);
// 拼接图片和根据坐标点预估位置写入文字
let stream = await gm()
// 在(0,0)位置处放入背景图
.in('-page', '+0+0')
.in(think.ROOT_PATH + '/www/static/image/poster/coupon_template.jpg')
// 在(240,138)位置处放入圆形头像
.in('-page', '+240+138')
.in('tmp/img/circleAvatar_'+ variable +'.png')
// 在(327,987)位置处放入二维码
.in('-page', '+327+987')
.in(qrcode_local_path_output)
// 拼接
.mosaic()
//写完赛日期
.fill('#ffffff')
// 加载指定字体
.font("msyh.ttf")
.fontSize(30)
// 在指定位置写入名字
.drawText(387, 195, decodeURI(name))
// 写手机号
.fill('#ffffff')
.font("msyh.ttf")
.fontSize(30)
.drawText(387, 240, decodeURI(phone))
// 写有效期
.fill('#ffffff')
.font("msyh.ttf")
.fontSize(20)
.drawText(405, 887, decodeURI(coupon.validto.split(" ")[0]))
// 保存最终的海报
let gmWrite = think.promisify(stream.write, stream);
await gmWrite(qrcode_local_path_output);
至此大功告成,当然代码并不是完整的,这里只是大致介绍。后面还有很多可以优化,例如可以将生成的海报保存下来,以便下次直接使用。下次生成海报的时候可以先判断之前是否已经生成了海报,如果已经生成了,那么直接从数据库把海报的url读取出来。
参考:
头像转圆形
https://zhuanlan.zhihu.com/p/137131729
带参二维码
在线免费生成条码:
Comments