精华 node文字转图片
发布于 7 年前 作者 Amastyer 20600 次浏览 来自 分享

为啥要搞这玩意?

今天老板提了需求,要在服务端生成邀请卡,嗯…,简单的说就是把要这张: webwxgetmsgimg.jpg 变成差多这样的: card.png 后端搞ruby的哥们搞了个html转图片,说转得太慢了,我就把这坑接下来了 所以睡前就倒腾了下,搞了个简单的实现

解决思路

文字转svg -> svg转png -> 合并图片

相关轮子

  • images Node.js 轻量级跨平台图像编解码库,不需要额外安装依赖
  • text-to-svg 文字转svg
  • svg2png svg转png图片

示例代码

'use strict';

const fs = require('fs');
const images = require('images');
const TextToSVG = require('text-to-svg');
const svg2png = require("svg2png");
const Promise = require('bluebird');

Promise.promisifyAll(fs);

const textToSVG = TextToSVG.loadSync('fonts/文泉驿微米黑.ttf');

const sourceImg = images('./i/webwxgetmsgimg.jpg');
const sWidth = sourceImg.width();
const sHeight = sourceImg.height();

const svg1 = textToSVG.getSVG('魏长青-人人讲App', {
  x: 0,
  y: 0,
  fontSize: 24,
  anchor: 'top',
});

const svg2 = textToSVG.getSVG('邀请您参加', {
  x: 0,
  y: 0,
  fontSize: 16,
  anchor: 'top',
});

const svg3 = textToSVG.getSVG('人人讲课程', {
  x: 0,
  y: 0,
  fontSize: 32,
  anchor: 'top',
});

Promise.coroutine(function* generateInvitationCard() {
  const targetImg1Path = './i/1.png';
  const targetImg2Path = './i/2.png';
  const targetImg3Path = './i/3.png';
  const targetImg4Path = './i/qrcode.jpg';
  const [buffer1, buffer2, buffer3] = yield Promise.all([
    svg2png(svg1),
    svg2png(svg2),
	svg2png(svg3),
  ]);

  yield Promise.all([
    fs.writeFileAsync(targetImg1Path, buffer1),
    fs.writeFileAsync(targetImg2Path, buffer2),
    fs.writeFileAsync(targetImg3Path, buffer3),
  ]);

  const target1Img = images(targetImg1Path);
  const t1Width = target1Img.width();
  const t1Height = target1Img.height();
  const offsetX1 = (sWidth - t1Width) / 2;
  const offsetY1 = 200;

  const target2Img = images(targetImg2Path);
  const t2Width = target2Img.width();
  const t2Height = target2Img.height();
  const offsetX2 = (sWidth - t2Width) / 2;
  const offsetY2 = 240;

  const target3Img = images(targetImg3Path);
  const t3Width = target3Img.width();
  const t3Height = target3Img.height();
  const offsetX3 = (sWidth - t3Width) / 2;
  const offsetY3 = 270;

  const target4Img = images(targetImg4Path);
  const t4Width = target4Img.width();
  const t4Height = target4Img.height();
  const offsetX4 = (sWidth - t4Width) / 2;
  const offsetY4 = 400;

  images(sourceImg)
  .draw(target1Img, offsetX1, offsetY1)
  .draw(target2Img, offsetX2, offsetY2)
  .draw(target3Img, offsetX3, offsetY3)
  .draw(target4Img, offsetX4, offsetY4)
  .save('./i/card.png', { quality : 90 });
})().catch(e => console.error(e));

注意事项

  • text-to-svg需要中文字体的支持,不然中文会乱码

在我的破电脑上执行一次只花了500多毫秒,感觉足够了,分享出来希望能给大家一个参照 厚脸皮的问一下,能加个精不?😂

16 回复

👍

来自酷炫的 CNodeMD

gm画也可以的,不过依赖要多一些,你这个是一个好的解决方案

@i5ting 之所以选择images就是因为不需要额外安装依赖,省却运维麻烦

感觉可以防爬虫

发现好东西了

mark一下,正在学习中。这段程序对我来说最难的还是异步的处理,正在加紧学习中。

mark,学习了

来自酷炫的 CNodeMD

Flash 和 H5 轻轻松松就能实现这个要求啊。

楼主 你好 请问 mac 64位的不支持 images包 还有什么好的解决方案吗? error:images does not yet support your current environment: OS X 64-bit

@1049229070 感觉sharp比image好用很多,你可以试一下, 楼主这个功能用sharp很容易实现, 性能比image要高很多, 因为可以不用调svg2png()这个方法, svg转png是真的很慢

@nar142857 我使用sharp重写了,代码奉上😂

示例代码

const TextToSVG = require('text-to-svg');
const sharp = require('sharp')
console.time("png")
const textToSVG = TextToSVG.loadSync('./DENG.TTF');

const svg1 = textToSVG.getSVG('魏长青-人人讲App', {
    x: 0,
    y: 0,
    fontSize: 24,
    anchor: 'top',
});

const svg2 = textToSVG.getSVG('邀请您参加', {
    x: 0,
    y: 0,
    fontSize: 16,
    anchor: 'top',
});

const svg3 = textToSVG.getSVG('人人讲课程', {
    x: 0,
    y: 0,
    fontSize: 32,
    anchor: 'top',
});


(async function run(){
    const sourceImg = sharp('./bg.png')
    const target1Img = sharp(Buffer.from(svg1))
    const target2Img = sharp(Buffer.from(svg2))
    const target3Img = sharp(Buffer.from(svg3))
    
    const [
        {width:sWidth , height:sHeight },
        {width:t1Width , height:t1Height },
        {width:t2Width , height:t2Height },
        {width:t3Width , height:t3Height }]= await Promise.all([
            sourceImg.metadata(),
            target1Img.metadata(),
            target2Img.metadata(),
            target3Img.metadata()])

    const offsetX1 = parseInt((sWidth - t1Width) / 2);
    const offsetY1 = 200;
    
    const offsetX2 = parseInt((sWidth - t2Width) / 2);
    const offsetY2 = 240;
    
    const offsetX3 = parseInt((sWidth - t3Width) / 2);
    const offsetY3 = 270;

    const [target1Buffer,target2Buffer,target3Buffer] = await Promise.all([
        target1Img.toBuffer(),
        target2Img.toBuffer(),
        target3Img.toBuffer()])

    await sourceImg
        .composite([
            {input:target1Buffer,left:offsetX1, top:offsetY1},
            {input:target2Buffer,left:offsetX2, top:offsetY2},
            {input:target3Buffer,left:offsetX3, top:offsetY3}
        ])
        .sharpen()
        .withMetadata()
        .png()
        .toFile('./card.png')
})()

console.timeEnd("png")

为啥 需要从 text -> svg -> png,我看网上有从 text -> png 的库呢

只需要一个canvas就够了啊,用不着整这么麻烦

示例

const fs = require('fs').promises;
const { createCanvas, loadImage, registerFont } = require('canvas');

async function draw() {
  const bg = await loadImage('./bg.png');

  const canvas = createCanvas(bg.width, bg.height);
  const ctx = canvas.getContext('2d');

  ctx.drawImage(bg, 0, 0, bg.width, bg.height);

  ctx.textAlign = 'center';
  ctx.textBaseline = 'top';

  registerFont('msyh.ttc', { family: 'msyh' });

  ctx.fillStyle = '#333';
  ctx.font = '24px "msyh"';
  ctx.fillText('魏长青-人人讲App', bg.width / 2, 200, bg.width);

  ctx.font = '16px "msyh"';
  ctx.fillText('邀请您参加', bg.width / 2, 240, bg.width);

  ctx.fillStyle = '#000';
  ctx.font = '32px "msyh"';
  ctx.fillText('人人讲课程', bg.width / 2, 270, bg.width);
  
  const qrcode = await loadImage('./qrcode.png');

  ctx.drawImage(qrcode, (bg.width - qrcode.width) / 2, 400, qrcode.width, qrcode.height);
  
  return canvas;
}

async function save() {
  try {
    const canvas = await draw();
    await fs.writeFile('./a.jpg', canvas.toBuffer());
    console.log('保存成功!');
    console.timeEnd('img');
  } catch (e) {
    console.error(e);
  }
}

console.time('img');
save();

回到顶部