|
/** |
|
* 说明信息 |
|
* 1.目录结构: |
|
* ├── avatar.jpg |
|
* ├── qrcode.jpg |
|
* ├── share-image.js |
|
* ├── simhei.ttf |
|
* └── template.jpg |
|
* 2.备注:若出现资源路径异常时,则使用path.join(__dirname, "./simhei.ttf")的方式进行资源加载 |
|
* 3.示例: |
|
* share.genShareImage({ |
|
* backgroudPath: "template.jpg", |
|
* avatarPath: "avatar.jpg", |
|
* qrcodePath: "qrcode.jpg", |
|
* userName: "semlinker", |
|
* words: 2888, |
|
* likes: 188, |
|
* outFilePath: path.join(__dirname, `public/images/${+new Date()}.png`) |
|
* }) |
|
*/ |
|
|
|
const sharp = require("sharp"); |
|
const TextToSVG = require("text-to-svg"); |
|
const path = require("path"); |
|
|
|
// 加载字体文件 |
|
const textToSVG = TextToSVG.loadSync(path.join(__dirname, "./simhei.ttf")); |
|
|
|
// 创建圆形SVG,用于实现头像裁剪 |
|
const roundedCorners = new Buffer( |
|
'<svg><circle r="90" cx="90" cy="90"/></svg>' |
|
); |
|
|
|
// 设置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); |
|
} |
|
|
|
/** |
|
* 图层叠加生成分享图片 |
|
* @param {*} options |
|
* |
|
*/ |
|
async function genShareImage(options) { |
|
const { backgroudPath, avatarPath, qrcodePath, |
|
userName, words, likes, outFilePath |
|
} = options; |
|
|
|
// 背景图片 |
|
const backgroudBuffer = sharp(path.join(__dirname, backgroudPath)).toBuffer({ |
|
resolveWithObject: true |
|
}); |
|
|
|
const backgroundImageInfo = await backgroudBuffer; |
|
// 头像图片 |
|
const avatarBuffer = await genCircleAvatar(path.join(__dirname, avatarPath)); |
|
|
|
// 二维码图片 |
|
const qrCodeBuffer = await sharp(path.join(__dirname, qrcodePath)) |
|
.resize(180) |
|
.toBuffer({ |
|
resolveWithObject: true |
|
}); |
|
|
|
// 用户名 |
|
const userNameSVG = textToSVGFn(userName); |
|
// 用户数据 |
|
const userDataSVG = textToSVGFn(`写了${words}个字 收获${likes}个赞`); |
|
const userNameBuffer = await sharp(new Buffer(userNameSVG)).toBuffer({ |
|
resolveWithObject: true |
|
}); |
|
const userDataBuffer = await sharp(new Buffer(userDataSVG)).toBuffer({ |
|
resolveWithObject: true |
|
}); |
|
|
|
const buffers = [avatarBuffer, qrCodeBuffer, userNameBuffer, userDataBuffer]; |
|
// 图层叠加参数列表 |
|
const overlayOptions = [ |
|
{ top: 150, left: 230 }, |
|
{ top: 861, left: 227 }, |
|
{ |
|
top: 365, |
|
left: (backgroundImageInfo.info.width - userNameBuffer.info.width) / 2 |
|
}, |
|
{ |
|
top: 435, |
|
left: (backgroundImageInfo.info.width - userDataBuffer.info.width) / 2 |
|
} |
|
]; |
|
|
|
// 组合多个图层:图片+文字图层 |
|
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.'); |
|
}); |
|
} |
|
|
|
/** |
|
* 生成圆形的头像 |
|
* @param {*} avatarPath 头像路径 |
|
*/ |
|
function genCircleAvatar(avatarPath) { |
|
return sharp(avatarPath) |
|
.resize(180, 180) |
|
.overlayWith(roundedCorners, { cutout: true }) |
|
.png() |
|
.toBuffer({ |
|
resolveWithObject: true |
|
}); |
|
} |
|
|
|
module.exports = { |
|
genShareImage |
|
}; |
nice