Sharp 牛刀小试之生成专属分享图片

Sharp 牛刀小试之生成专属分享图片

前阵子公司的产品经理找我谈个需求,希望能为每个用户生成专属的资讯分享图片及让开通专栏的用户能够生成专属的文章分享图片。这两天刚好有空,就抽空预研了 “生成专属的资讯分享图片” 这个功能。

进入正题前,我们先来看一下最终实现的效果图:

 

需求分析

接下来我们来简单的介绍一下 “生成专属的资讯分享图片” 这个功能需求:

  1. 图片中有个区域能够显示分享用户的头像和昵称;
  2. 图片中需要显示用户的一些数据信息;
  3. 图片底部需要展示 App 的二维码信息。

了解完这个需求,我脑海中有三个可选方案:

  1. 在 App 端使用原生提供的 API 进行图片合成;
  2. 在服务端通过特定语言提供的图片处理库,进行图片合成;
  3. 在服务端根据不同的用户信息生成网页,然后使用 HTML 转 IMAGE 的库;或使用 iOS 企业证书过期填坑记 文章介绍过的神器 puppeteer 提供的 API 实现全屏截图功能。

由于对 App 端(Android 和 iOS)都不熟,只懂了点皮毛,所以打算就直接尝试第二种方案。确定使用第二种方案,我并没有马上动手开始开发,而是先在网上找一些相关的文章,因为觉得这个需求应该挺常见的。果然通过一番检索,找到了用程序生成一张在简书的专属分享图片这篇文章。文章作者对功能做了详细的分析,然后利用 Python 强大的图片处理库 Pillow 进行功能实现。建议有兴趣的同学,直接阅读原文。

虽然 Python 勉强算入门,结合 Pillow 的 API 文档,作者写的代码基本能看懂,但作为一个喜欢折腾的小前端,怎能不使用我们的 Node.js 来折腾一下呢?说做就做,当然马上到 npm 上挑选 Node.js 的图片处理库。经过一番筛选,最终选中了 sharp,这是为什么?当然是喜欢它的名字咯(嘿嘿,其实是看中它的高性能)。

High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images. Uses the libvips library. http://sharp.pixelplumbing.com/

再次感谢 用程序生成一张在简书的专属分享图片 该文章的作者,他把项目源码和资源都放到了 Github – jianshu_share 上。因为只是技术预研,我就直接使用该项目的图片资源,接下来我们来介绍一下具体实现。

实现步骤

  • 裁剪头像(方形 -> 圆形):通过查看 sharp 项目的说明文档,我发现了裁剪头像的方案,具体实现如下:
// 创建圆形SVG,用于实现头像裁剪
const roundedCorners = new Buffer(
  '<svg><circle r="90" cx="90" cy="90"/></svg>'
);

/**
 * 生成圆形的头像
 * @param {*} avatarPath 头像路径
 */
function genCircleAvatar(avatarPath) {
  return sharp(avatarPath)
    .resize(180, 180)
    .overlayWith(roundedCorners, { cutout: true })
    .png()
    .toBuffer({
      resolveWithObject: true
    });
}
  • 叠加背景图、头像和二维码图层:这个功能与 PS 的图层叠加类似,其实就是把裁剪过的头像和二维码,贴到背景图的指定位置。要实现这个功能也是需要使用 sharp 提供的 overlayWith 方法。
// 组合多个图层:图片+文字图层
  return buffers
    .reduce((input, overlay, index) => {
      return input.then(result => {
        console.dir(overlay.info);
        return sharp(result.data)
          .overlayWith(overlay.data, overlayOptions[index])
          .toBuffer({ resolveWithObject: true });
      });
    }, backgroudBuffer)
    .then((data) => {
      return sharp(data.data).toFile(outFilePath);
    }).catch(error => {
      throw new Error('Generate Share Image Failed.');
    });
  • 根据用户信息创建文本:在实现这个功能的时候,遇到了一个问题。因为官方的 API 没有提供文件创建图片的方法,最终参考了 sharp 项目中 How to dynamically write text to image? 这个 issue 中提供的方案,解决了这个问题。 即利用 text-to-svg 这个库,先把文本转换成 SVG,然后在利用 overlayWith 方法进行图层合并。
// 加载字体文件
const textToSVG = TextToSVG.loadSync(path.join(__dirname, "./simhei.ttf"));

// 设置SVG文本元素相关参数
const attributes = { fill: "white" };
const svgOptions = {
  x: 0,
  y: 0,
  fontSize: 32,
  anchor: "top",
  attributes: attributes
};

/**
 * 使用文本生成SVG
 * @param {*} text 
 * @param {*} options 
 */
function textToSVGFn(text, options = svgOptions) {
  return textToSVG.getSVG(text, options);
}
  • 叠加所有图层,生成最终的图片:实现最后一步的时候,也是遇到了一个大坑,官方没有提供对应的方法。幸运的是,最终也是通过项目中 Easier way to composite 3+ layers 这个 issue 提供的方案,完美解决了问题。

经过大半天地折腾,终于借助 Node.js 的 sharp 这个图片处理库,基本实现了上述的功能。由于源码过长,我直接放在 Gist 上,有兴趣的小伙伴可以查看 gen-share-image.js 完整源码。

总结

本文主要介绍了如何利用 Node.js 的 sharp 图片处理库,生成专属的分享图片。源码中有很多细节需要处理,如动态获取头像、根据参数动态生成文本信息、异常处理及基于 Koa、Egg.js 或 Express 框架,创建对应的 API 服务等。 gen-share-image.js 源码只是介绍了完整的思路和实现方式,实际开发的时候,在根据具体需求进行调整。虽然目前是已经基于 Koa 开发了一个简单API服务,但还有一些流程需要优化,有兴趣了解具体细节或有更好方案的小伙伴可以给我留言哈

Comments 5

发表评论

电子邮件地址不会被公开。 必填项已用*标注