sharp text2png

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

Keita Imatomi
2020-2-20 16:38

目的

この note のページのogp画像を以下の仕様で自動生成したい。

  • ベースとなる画像の上に note のタイトル文字を合成する。
  • タイトルは2行まで。それより文字数が多い場合はうまいこと省略する。

結果

できた。

image

OSS

sharp

すでにこのサイトでは、画像処理に sharp を使用しているので、ここでも sharp を使用する。 以前は jimp を使用していたが、 sharp の方が扱いやすい上に処理も早かった。

sharp

const sharp = require('sharp')   await sharp('ベース画像') .composite([{ input: タイトル画像 }]) .toFile('出力画像')) }

text2png

sharp は文字を扱えないので、文字を画像化するためにこれを使用する。

  • text2png() に文字列とオプションを与えると画像バッファを返してくれる。

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('出力画像')
}
こちらもどうぞ