如何使用 node.js 生成活动海报


在平时的项目中,我们经常会有生成活动营销海报的需求,一般是有一张海报的背景图,然后动态获取用户头像,将头像裁剪成圆形,动态获取用户昵称,动态生成用户专属的二维码,最后将用户的圆形头像,昵称和二维码合并到背景图上,就生成了该用户专属的分享海报。上面提到的几点,可能任何一点都会让你找半天资料,最后还不一定有效,耽误项目进度,影响自己心情,所以这里分享一下最近的一些经验,让你看完这篇文章,就可以在 node.js 的环境中完美的实现这个需求。


首先,概括的来看这个需求,我们需要将一些图像附着在一张背景图上面,合成一张新的图片。通过网上查找资料,发现主要有这么几个库或者包可以实现图片合成的功能:GraphicsMagicimagessharp

  1. 要使用 GraphicsMagic,要先在电脑上安装 GraphicsMagic,然后在项目中使用 npm 安装 gm 依赖,去调用 GraphicsMagic 的功能。我本地是 Mac OS 系统,服务器端是 Ubuntu 系统,我感觉给本机安装额外软件的这种方式很不友好,很麻烦,可能有坑,就没选择这种方式。
  2. 要使用 images 这个包,直接在项目里面使用 npm 安装就好,不过上面的一系列功能,只使用 images 是无法完成的,需要 images 和 sharp 一起使用。这个时候,有个坑就出现了。使用 npm 安装的 images 是 3.0.2 版本,已经一年没有更新过了,然后它存在系统兼容性问题,在 Mac OS 系统上,它最高支持 node 10,而在 Linux 系统上,它最高只支持 node 8,而因为 images 要和 sharp 一起用,而 sharp 要求 node 版本必须大于 10.13.0,所以在 Linux 系统上,这两个包没法同时使用。。。本来刚开始上面的所有功能在本地的 Mac 上使用 images 和 sharp 结合的方式都已经开发完成了,结果部署到 Ubuntu 上不能用。所以最后这种方式也被弃用了。
  3. 最后通过查询 sharp 的文档,发现完全依靠 sharp 也能实现上面图片合成的功能,所以最后就用了 sharp 来完成。

我们先来把用户的方形头像转化为圆形头像。如果是在前端页面展示,那么直接给 img 标签设置 border-radius 样式就可以了。但如果是在 node.js 的服务端,有一张正方形的头像图片,需要生成一个圆形的头像,应该怎么做呢?

首先,如果我们获取的用户头像是一张网络图片,例如用户的微信头像,我们先把方形头像保存成本地图片。我们进行一次 GET 请求,url 就写头像地址,网络请求返回给我们的就是图片数据,然后我们把图片数据写入到本地文件就可以了。网络请求使用的是 request-promise 包。要注意的是,在请求中,要注明编码 encoding 为 null 或者 “binary”,这样获取到的数据保存成图片后,才可以正常打开,不然获取的数据保存后的图片是无法打开的。

import rp from 'request-promise'
import fs from 'fs'

const options = {
method: 'GET',
uri: 'http://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLaIphkicuZME4QIjsicNhjnAHoibSEcB0GpiaK95HX7tbnge3yESMWlIH1x9E9iaIHjzwCkias1gqyFymQ/132', // 头像地址
encoding: 'binary' // 或者也可以是 null
}
rp(options).then(response => {
fs.writeFileSync('avatar.png', response, 'binary')
})

假设我们要保存的头像文件为 avatar.png,调用 fs 的 writeFileSync 方法同步的将图片数据写入到文件中。注意第三个参数,也要写 ‘binary’。然后你就能发现,头像已经保存到了本地,并且可以正常打开,下面我们将方形头像转化为原型头像。

头像的转化用到了 sharp,所以要先在项目中使用 npm 安装 sharp 包。然后我们创建一个 Buffer 缓存区,这个 Buffer 缓存区读取的是一个 svg,这个 svg 里面是一个矩形(rect),因为我们最终想把原型头像的大小调整为 100*100,所以 svg 里面 rect 的 width 和 height 都是 100,rx 和 ry 用来表示圆角,把圆角设置为宽高的一半,就是一个圆形(其实是一个圆角矩形)。对 Buffersvg 不太了解的话,可以先去专门去了解一下。然后我们使用 sharp 的 composite 方法,将圆形的 svg 和方形的头像进行合成,就能得到一个圆形的头像。注意 blend 的类型有很多,自己可以分别尝试一下,使用 dest-in 可以实现需要的这种效果。

import sharp from 'sharp'

const roundedCorners = Buffer.from(
'<svg><rect x="0" y="0" width="100" height="100" rx="50" ry="50"/></svg>'
)
sharp('avatar.png')
.resize(100,100)
.composite([{input:roundedCorners, blend: 'dest-in'}])
.png()
.toFile('circleAvatar.png')

下面我们将二维码图片保存到本地,如果你得到的已经是一个二维码的图片,那你可以像上面那样,进行一次 GET 请求,然后将请求的结果直接保存成本地图片就可以。如果你得到的是一个链接,才准备将这个链接生成一个二维码,那你可以使用 qrcode 这个包。这个包用来生成二维码还是很好用的,可自由配置很多东西。

import QRCode from 'qrcode'

QRCode.toFile('qrcode.png','http://weixin.qq.com/q/02QNmshSQLdhl10000g077',{
margin: 2,
width: 240
})

下面我们将用户昵称转化为本地图片。我们获取的用户昵称是一个字符串,要先使用 text-to-svg 这个包,把它转为 svg。可以设置字体,字体大小,颜色等很多参数。然后再通过 sharp 将 svg 文件转为 png 文件,因为我们最终合成海报时需要的是图片文件。

import TextToSVG from 'text-to-svg'
import sharp from 'sharp'

const textToSVG = TextToSVG.loadSync()
const attributes = {fill: 'white'};
const options = {x: 0, y: 0, fontSize: 40, anchor: 'top', attributes: attributes};
const svg = textToSVG.getSVG('mofiter',options)
fs.writeFileSync('nickname.svg',svg)
await sharp('nickname.svg').png().toFile('nickname.png')

当我们得到了圆形头像图片 circleAvatar.png,二维码图片 qrcode.png,昵称图片 nickname.png,我们就可以使用 sharp 的 composite 方法将这些图片合并到海报背景图片 background.png 上面,生成个人专属的海报。要合并的图片位于背景上的什么位置,可以通过 left 和 top 指定。

sharp('background.png').composite([{input:'circleAvatar.png',left:65,top:80},{input:'nickname.png',left:180,top:80},{input:'qrcode.png',left:760,top:1620}]).png().toFile('poster.png')

通过上面的这些步骤,就可以生成一张完整的海报啦,在真正用到的时候,要注意 ES6 的语法,还有同步和异步的问题,希望本文可以对你有所帮助。


 评论