Nest + Sharp 实现给表情包gif加文字

前言

上篇文章 纯前端js给表情包gif加文字,你就是"表情包大战"最靓的仔! 介绍了如何通过纯前端js给表情包gif加文字。但毕竟客户端无论能力,还是性能、兼容性都有限,比如介绍的库都只支持Web,不支持其它客户端例如小程序。尤其这种文件编码能力,其实在服务端处理能力更好,能用的库或者服务端的工具都很多,所以这种需求尽量还是放在后端实现。

本文就介绍下后端Nodejs上,使用Nest框架,利用第三方库Sharp,给表情包gif加文字。

Nest

如果没有安装过需要全局安装

bash 复制代码
npm i -g @nestjs/cli

新建 Nest 工程

bash 复制代码
nest new nest-sharp-gif

Sharp

github.com/lovell/shar...

Sharp 是一个高性能的 Node.js 图像处理库,可以非常快的速度来调整图片的大小,同时也支持压缩图片、合成、旋转、提取图片等等。支持多种格式图片,包括:JPEG,PNG,WebP,GIF,AVIF 和 TIFF。

光神之前也介绍过sharp的压缩功能:Nest + Sharp 实现了一个 gif 压缩工具,帮我省不少钱

gif加文字,就得用到sharp的合并功能。

首先安装npm包:

bash 复制代码
npm install sharp

合并用到了sharp api里的composite方法,用法:

javascript 复制代码
import * as sharp from 'sharp';

const gifBuffer // gif 文件流
const image = await sharp(gifBuffer, { animated: true })
const metadata = await image.metadata();

// 获取svg buffer,注意需要设置svg的高宽同原图一致
const textImage = Buffer.from(`
<svg width="${metadata.width}px" height="${metadata.pageHeight}px" xmlns="http://www.w3.org/2000/svg">
  <text x="0" y="30px" font-size="30px" fill="red">Text</text>
</svg>`);
const buffer = await image
  .composite([
    { input: textImage, tile: true } // 可以添加多个图层
  ])
  .toBuffer();

先弄个gif原图片,这里就用掘金的cdn url:p1-juejin.byteimg.com/tos-cn-i-k3...

通过fetch获取文件流:

typescript 复制代码
import fetch from 'node-fetch';

const url = "https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/eb1e0ec360f041729bde3420d0988b45~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=300&h=240&s=1541600&e=gif&f=30&b=8c7c5b";
// 用fetch获取gif文件流
const image1 = await fetch(url);
const gifBuffer = await image1.buffer();

效果图:

需要注意两点:

  • 需要设置tile: true,才可以在gif每帧上都合成,否则只会合成第一帧。
  • 另一个需要注意,svg的高度必须跟原gif图片高度一致,否则会重复叠加图层,会出现文字循环滚动效果,如下例。
javascript 复制代码
<svg width="100px" height="30px" xmlns="http://www.w3.org/2000/svg">
  <text x="0" y="30px" font-size="30px" fill="red">Text</text>
</svg>

效果图:

例子

弄清楚了composite用法,就可以写例子了,添加nest controller test方法:

javascript 复制代码
@Get('test1')
async test1(
  @Res() res: Response
) {

  const url = "https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/eb1e0ec360f041729bde3420d0988b45~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=300&h=240&s=1541600&e=gif&f=30&b=8c7c5b";
  // 用fetch获取gif文件流
  const image1 = await fetch(url);
  const gifBuffer = await image1.buffer();

  // 用sharp获取图片高宽
  const image = await sharp(gifBuffer, { animated: true });
  const metadata = await image.metadata();

  const width = metadata.width;
  const height = metadata.pageHeight; // 注意是pageHeight,不是height
  const text = '不错不错';
  const x = width / 2;
  const y = 50;
  const fontFamily = 'Arial, sans-serif';
  const fontSize = 30;
  const color = 'white';
  const stroke = 'red';

  // 获取svg buffer,注意需要设置svg的高宽同原图一致
  const textImage = Buffer.from(`
<svg width="${width}px" height="${height}px" xmlns="http://www.w3.org/2000/svg">
    <text
      text-anchor="middle"
      y="${y}" x="${x}"
      font-family="${fontFamily}"
      font-size="${fontSize}px"
      fill="${color}"
      stroke="${stroke}"
      >${text}</text>
</svg>`
  );
  const buffer = await image
    .composite([
      { input: textImage, tile: true } // 可以添加多个图层
    ])
    .toBuffer();

  // response输出文件流
  res.set('Content-Type', 'image/gif');
  res.send(buffer);
}

直接浏览器访问下http://localhost:3000/test1就能看到效果,因为response设置了Content-Type,浏览器会自动preview图片。

Web

有两种交互方式:

  1. 上例的cdn url方式,gif文件是一个url。
  2. 通过客户端上传文件。
html 复制代码
<div>
    <!-- url 转换 -->
    <button id="btn1">test1</button>
    <br>
    <img id="img1" />
</div>
<div>
    <!-- 上传文件 转换 -->
    <input id="input1" type="file">
    <br>
    <img id="img2" />
</div>

<script>
    // url 转换
    const btn1 = document.getElementById('btn1');
    const img1 = document.getElementById('img1');
    btn1.onclick = async function () {
        const res = await fetch('http://localhost:3000/test1', { method: "GET" });
        const blob = await res.blob();
        const base64 = URL.createObjectURL(blob);
        img1.src = base64;
    }

    // 上传文件 转换
    const input1 = document.getElementById('input1');
    const img2 = document.getElementById('img2');
    input1.onchange = async function (e) {
        const file = e.target.files[0] || e.dataTransfer.files[0];
        const formData = new FormData();
        formData.append('file', file);
        const res = await fetch('http://localhost:3000/upload', { method: "POST", body: formData });
        const blob = await res.blob();
        const base64 = URL.createObjectURL(blob);
        img2.src = base64;
    }
</script>

nest upload方法,需要引用npm install @types/multer -D

typescript 复制代码
@Post('upload')
  @UseInterceptors(FileInterceptor('file'))
  async gifAddTextsUpload(
    @UploadedFile() file: Express.Multer.File,
    @Res() res: Response
  ) {

    const image = await sharp(file.buffer, { animated: true });
    const metadata = await image.metadata();
    // 同上实现

效果:

其它创建图层方式

composite.input也支持直接定义text信息,但是经测试,如果不设置width\height,会有重复文字问题;设置了则会有滚动效果,都没打到预期,可能是我用的不对。

typescript 复制代码
.composite([
  // { input: textImage, tile: true } // 可以添加多个图层
  {
    input: {
      text: {
        text: 'Text',
        rgba: true,
        font: 'Arial',
        width: metadata.width,
        height: metadata.pageHeight,
      },
    },
    tile: true
  }
])

效果:

也可以用sharp先把文字转成png图片,然后再当做图层合成,但是经测试也会有文字滚动效果,问题同上,效果也跟上面一样。

javascript 复制代码
const overlay = await sharp({
  text: {
    text: 'Text',
    rgba: true,
    font: 'Arial',
    width: metadata.width,
    height: metadata.pageHeight,
  },
}).png().toBuffer();

更多用法

  • 水印
    • composite.input是个文件流,所以也支持传入图片,再加上背景透明,就可以做成水印效果。
  • 多个文字
    • 因为composite参数是数组,所以支持多个图层,也就支持添加多个文字或水印。
  • 分段显示文字
    • 有的gif需要先后不同位置显示文字,就跟台词一样。思路是先把gif按帧拆分成多个gif,然后分别给合并对应文字,然后再拼接成一个gif。
    • 拆分gif可以用optionspagespage俩参数,可以按帧拆分。
    • 拼接的话,找了几个api,试过没效果,有找到的可以分享下。

总结

本文主要介绍了如何使用Sharp第三方图像处理库,通过Nest调用api,给gif图片添加文字内容。相比前端方案 纯前端js给表情包gif加文字,你就是"表情包大战"最靓的仔!,后端nodejs里转换更稳定,更精确,支持的功能也就更多。

源码:github.com/markz-demo/...

相关推荐
垣宇1 小时前
Vite 和 Webpack 的区别和选择
前端·webpack·node.js
爱吃南瓜的北瓜2 小时前
npm install 卡在“sill idealTree buildDeps“
前端·npm·node.js
翻滚吧键盘2 小时前
npm使用了代理,但是代理软件已经关闭导致创建失败
前端·npm·node.js
浪九天3 小时前
node.js的版本管理
node.js
浪九天5 小时前
node.js的常用指令
node.js
浪九天7 小时前
Vue 不同大版本与 Node.js 版本匹配的详细参数
前端·vue.js·node.js
小纯洁w18 小时前
Webpack 的 require.context 和 Vite 的 import.meta.glob 的详细介绍和使用
前端·webpack·node.js
熬夜不洗澡19 小时前
Node.js中不支持require和import两种导入模块的混用
node.js
bubusa~>_<20 小时前
解决npm install 出现error,比如:ERR_SSL_CIPHER_OPERATION_FAILED
前端·npm·node.js
天下皆白_唯我独黑21 小时前
npm 安装扩展遇到证书失效解决方案
前端·npm·node.js