🧚‍♂️

nextjsでnode-pureimageを使ったJSオンリーな動的画像生成

2021/08/09に公開

nextjsで動的画像生成をしたくなり、node-canvasだとnode以外の依存関係がちょっと困るケースがあったので、PureJSで実行できるnode-pureimageでなんとかならないか色々やってみた

出来た結果

// /api/image/sample

import { NextApiHandler, NextApiResponse } from "next"

// @ts-ignore
import { make, encodePNGToStream } from "pureimage"

import getStream from 'get-stream'
import { PassThrough } from 'stream'

const generateSampleImage = async () => {
  // 画像生成
  const img = make(100, 100)
  const ctx = img.getContext("2d")

  ctx.fillStyle = 'black'
  ctx.fillRect(0, 0, 100, 100)
  ctx.beginPath()
  ctx.fillStyle = 'red'
  ctx.arc(50, 50, 40, Math.PI, 0)
  ctx.fill()
  ctx.closePath()

  // bufferに変換
  const stream = new PassThrough()
  await encodePNGToStream(img, stream)
  const buffer = await getStream.buffer(stream)
  return buffer
}

// レスポンス返却
const setResponse = (res: NextApiResponse<any>, buf: Buffer) => {
  res.setHeader('Content-Type', 'image/png')
  res.setHeader('Content-Length', buf.length)
  res.setHeader('Cache-Control', `public, max-age=86400`)
  res.send(Buffer.from(buf))
  res.status(200).end()
}

// ハンドラ
const handler: NextApiHandler = async (req, res) => {
  const buf = await generateSampleImage()
  setResponse(res, buf)
}

export default handler

node-pureimageはデフォルトでは画像書き込みするメソッドしかないのだが、nextからレスポンスとして返すにはbuffer化する必要がある。
今回はget-streamを利用したBufferに変換した。

また、テキストを書き込む場合は、あらかじめregisterFontする必要がある

// @ts-ignore
import { make, registerFont, encodePNGToStream } from "pureimage"

// ....
// ....
const fontPath = path.join("./fonts", "MPLUSRounded1c-Light.ttf")
const fnt = registerFont(fontPath, "MPLUSRounded1c")
await fnt.loadSync()

const ctx = img.getContext("2d")

ctx.fillStyle = "blue"
ctx.font = "78pt 'MPLUSRounded1c'"
ctx.fillText("foo", 20, 500)

ただ、フォントについては、下記のような未調査の点が残った

  • opentype.jsを利用しているようなのだが、otfファイルがうまく読み込めないっぽい
  • フォントサイズが80ptを超えると、next.jsがタイムアウトしがちになる

そのため、OGPを生成したい要件であれば、verce/og-imageに頼るのが良い可能性がある。
HTMLで生成し、puppeteerでスクリーンショットしており、なかなか迫力があるのでコードを見てみるのもお勧め。

GitHubで編集を提案

Discussion