サーバー側で画像に文字を合成する

imatomix
2020年2月20日 16:38

目的

この note のページのogp画像を以下の仕様で自動生成したい。
  • ベースとなる画像の上に note のタイトル文字を合成する。
  • タイトルは2行まで。それより文字数が多い場合はうまいこと省略する。

結果

できた。
image

OSS

すでにこのサイトでは、画像処理に sharp を使用しているので、ここでも sharp を使用する。 以前は jimp を使用していたが、 sharp の方が扱いやすい上に処理も早かった。
const sharp = require('sharp')   await sharp('ベース画像') .composite([{ input: タイトル画像 }]) .toFile('出力画像')) }

sharp は文字を扱えないので、文字を画像化するためにこれを使用する。
  • text2png() に文字列とオプションを与えると画像バッファを返してくれる。
const text2png = require('text2png')   const options = { font: '72px NotoSansJP-Bold', localFontPath: '/fonts/NotoSansJP-Bold.otf', // フォントファイルを使用できる localFontName: 'NotoSansJP-Bold', color: '#345', lineSpacing: 20 } const titleImage = text2png('タイトル文字列', options)

タイトルの文字列

タイトルが長すぎるとタイトル画像がベース画像よりも大きくなって、sharp に怒られる。 なのでタイトル文字列を以下のように編集する関数を作る
  • 一定文字数の箇所で改行を入れる
  • ー定文字数以上は削除して末尾に .. をつける
  • 半角と全角で横幅は異なるので、半角英数は1文字、全角は2文字として考える。
  • ひとまず、半角カタカナは気にしない。キリッ
function proofread(str) { let count = 0 let lineCount = 1 let newStr = '' const lineLength = 22 // 1行あたりの文字数 for (let i = 0; i < str.length; i++) { encodeURI(str.charAt(i)).length >= 4 ? (count += 2) : (count += 1) newStr += str.slice(i, i + 1) if (count > lineLength) { if (lineCount === 1) { newStr += '\n' // 改行を追加 count = 0 lineCount += 1 } else { newStr += '..' break // ループ終了 } } } return newStr }

最終形態

  • こんな感じにシンプルに。
const sharp = require('sharp') const text2png = require('text2png') const proofread = require('./proofread')   async function createThumImage(title) { const options = { font: '72px NotoSansJP-Bold', localFontPath: '/fonts/NotoSansJP-Bold.otf', localFontName: 'NotoSansJP-Bold', color: '#345', lineSpacing: 20 } const noteTitle = proofread(title) const titleImage = text2png(noteTitle, options) await sharp('ベース画像') .composite([{ input: titleImage }]) .toFile('出力画像') }