アイキャッチ画像作成ツールをサーバレスで実装してみた
開発者ブログの顔ともいえるアイキャッチ画像、私は毎回 Canva を利用して作成をしていますが、シンプルなデザインのものであればテンプレート化し、もっと楽ができないかと考えました。
そこで今回は、ブログのアイキャッチ画像を自動生成するツールの実装から公開までの流れを紹介していこうと思います。
目次
はじめに
こんにちは。
クラウドソリューション第二グループのwatanabe.tです。
ブログページを開いたとき、一番最初に目に入ってくるのがアイキャッチ画像です。
これが適当な画像だと、読者の目を引くことができず、良い記事なのに読んでもらえない!という事態を引き起こしてしまいます。
しかし、毎回ゼロから構成を考え、アイキャッチ画像を作成するのは時間と労力がかかります。
そこで今回はアイキャッチ画像を自動生成するWebアプリを作成し、その手間を削減していきます。
今回は簡単のため、タイトルと筆者名をリクエストパラメータとして渡せば、生成されたアイキャッチ画像がダウンロードできるだけを実装しますが、
流行りの生成AIと組み合わせることで、よりユニークなアイキャッチ画像を生成することも可能です。
アーキテクチャ概要
今回はシンプルに以下のサーバレスアーキテクチャで実装しました。
画像生成の処理が非常にシンプルなため、 API Gateway から Lambda を呼び出し、そこで画像の生成を行っているだけです。
Web UIを備えるアプリケーションにしても良かったかもしれませんが、SlackなどからIntegrationで呼び出した方が使い勝手が良いかと思い、フロントエンドアプリの実装は行っていません。
なお、今回は Lambda 上で画像処理を行うライブラリとして sharp を、テキストを画像データに変換するライブラリとして text2png を利用しています。
本記事で使用した環境/ライブラリは以下の通りです。(Serverless Frameworkのみ少し古いです)
- Node.js: v20.11.0
- sharp: 0.33.4
- text2png: 2.3.0
- serverless: 3.38.0
Serverless Frameworkの設定
まずはアプリケーションを開発・デプロイするため、Serverless Frameworkを利用していきます。
といっても特段難しい設定もなく、 serverless create コマンドでプロジェクトのひな型を作成した後、 serverless.yml を以下のように書き換えます。
service: generate-blog-eyecatch
frameworkVersion: '3'
provider:
name: aws
runtime: nodejs18.x
region: ap-northeast-1
package:
patterns:
- '*.png'
custom:
esbuild:
bundle: true
minify: false
sourcemap: true
exclude:
- "canvas"
- "aws-sdk"
keepOutputDirectory: true
functions:
generate:
handler: handler.generate
plugins:
- serverless-esbuild
ハマる可能性があるのは、 package 部分と exclude 部分ですね。
package は文字を合成する先のテンプレート画像をデプロイパッケージに含める必要があるため、 exclude は canvas で提供されるバンドルに esbuild が対応していないため、です。
コードの実装
文字から画像を生成し、背景画像と合成したうえで、生成された画像をレスポンスするコードです。
import fs from "fs";
import sharp from "sharp";
import text2png from "text2png";
import { proofread } from "./proofread";
import path from "path";
export async function generate(event) {
// 背景画像を読み込む
const image = sharp(
fs.readFileSync(path.resolve(__dirname, "./eyecatch_template.png"))
);
// タイトル文字列を書き込む
const title = event.title;
const options = {
font: "76px NotoSansJP-Bold",
localFontName: "NotoSansJP-Bold",
color: "#345",
lineSpacing: 20,
};
const noteTitle = proofread(title);
const titleImage = text2png(noteTitle, options);
// 著者文字列を書き込む
const author = event.author;
const authorOptions = {
font: "48px NotoSansJP-Bold",
localFontName: "NotoSansJP-Bold",
color: "#345",
lineSpacing: 20,
};
const noteAuthor = proofread(author);
const authorImage = text2png(noteAuthor, authorOptions);
const updatedImage = await image
.composite([
{ input: titleImage, left: 100, top: 100, gravity: "northwest" },
{ input: authorImage, left: 100, top: 530, gravity: "northwest" },
]) // 文字列を画像の指定位置に書き込む
.toBuffer();
// 画像データを返す
return {
statusCode: 200,
headers: { "Content-Type": "image/png" },
body: updatedImage.toString("base64"),
isBase64Encoded: true,
};
};
また、そのまま文字を画像に変換すると横にはみ出してしまうため、適度に改行を挟むようにしてあげます。
export const proofread = (str: string): string => {
const MAX_LINE_COUNT = 3;
let count = 0;
let lineCount = 1;
let newStr = "";
const lineLength = 22; // 1行あたりの文字数
for (let i = 0; i = 4 ? (count += 2) : (count += 1);
newStr += str.slice(i, i + 1);
if (count > lineLength) {
if (lineCount <= MAX_LINE_COUNT) {
newStr += "\n"; // 改行を追加
count = 0;
lineCount += 1;
} else {
newStr += "..";
break; // ループ終了
};
};
};
return newStr;
};
デプロイとテスト
では最後に、このコードをAWS上にデプロイし、動作確認してみましょう。
なお、Lambdaで画像を扱うためのCanvasモジュールを利用するためには、別途Lambda Layerをデプロイしておく必要があったため、リンク先のページから「Deploy」を選択し、事前にLayerをデプロイしておきます。
lambda-layer-canvas-nodejs
さて、AWS認証情報を適切に設定した後、 serverless deploy コマンドでAPI GatewayやLambdaが作成されたのち、コードがデプロイされます。
$ npm run deploy
> deploy
> sls deploy
Deploying generate-blog-eyecatch to stage dev (ap-northeast-1)
✔ Service deployed to stack generate-blog-eyecatch-dev (28s)
endpoint: https://xxxxxx.lambda-url.ap-northeast-1.on.aws/
functions:
generate: generate-blog-eyecatch-dev-generate (228 kB)
動作確認として、curlなどを使ってAPIを実行してみると、問題なく画像が生成されレスポンスされることが確認できました。
※ ここで生成した画像を本記事のアイキャッチ画像に設定しています。
$ curl -XPOST https://xxxxxx.lambda-url.ap-northeast-1.on.aws/ -d '{ "title": "アイキャッチ画像作成ツールをサーバレスで実装してみた", "author": "watanabe.t" }' -H 'Content-Type: application/json' --output output.png
おわりに
今回はブログを公開するまでの負荷を軽減するため、サーバレスでアイキャッチ画像を生成してみました。
一回当たりの実行時間も短いのと、そんなに頻繁にアイキャッチ画像が生成されるわけではないため、今回のような要件の場合はサーバレスアーキテクチャを採用するのが最適ですね。
この記事が少しでも役に立てば幸いです。