Nuxt+Firebaseで開発してるサービスのOGP画像を改善しようと、
いろいろ試してみたときの備忘録。
OGP画像の生成はクライアント側とサーバ側かがあるが、
Firestoreの変更に合わせて生成したいので、
Cloud Function上で利用できる方法を考えてた。
結果的に、
- sharpで画像を合成してベースをつくり
- ImageMagickで文字を追記していく
という構成になった。その試行錯誤の備忘録です。
やりたかったこと
背景画像+本の画像+文字みたいなOGP画像にしたい。
あと、座標の指定はめんどくさいので、楽な方法を探してみた。
SVGだとCSSも使えて、ブラウザ上で書くにできるので良さそう。
(と思ったけど、結果、だめだった...)
ためした方法とまとめ
試したのは以下の4パターン。
SVG+sharpで試した感じ。
node-canvasはおまけな感じ程度。
- sharp+SVG(style+foreginObject)
- SVGですべて構成する方法。
- フォントとか位置、サイズはstyleで設定
- 外部URLの画像が取得できずにNG
- sharp+SVG+画像は別で合成
- 1.の画像部分を別で合成するパターン
- カスタムフォントが設定できずNG
- sharp+SVG+ImageMagick
- 画像の合成部分のみsharpをつかい
- 枠線をSVG、文字はImageMagickで書き出し
- これを採用
- node-canvas+SVG
内部で利用しているSVGライブラリ自体で未対応っぽいので、
SVGのstyleや外部URLの画像、カスタムフォントなどは、
ブラウザ上以外の画像生成ではまだ未対応っぽい。
ほかにも、CloudFunction上でpuppeteerを使えるようだけど、
メモリをすごい使うっぽい記事を見てしまい、まだためしてない。。
以下試したコード。
SVG(style+foreginObject)
- sharpが、foreignObject+sytleに未対応...のためNG
- imageに外部URLを使っているとダメっぽい...
import sharp from "sharp"; await sharp("./input.svg") .png() .toFile("./output.png");
<?xml version="1.0"?> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewbox="0 0 1200 630" width="1200" height="630"> <style> @import url("https://fonts.googleapis.com/css?family=Noto+Sans+JP:500&display=swap&subset=japanese"); .contents { display: flex; justify-content: center; align-items: flex-end; width: 1200px; height: 630px; position: relative; } .book { height: calc(100% - 100px); } .text-contents { position: absolute; left: 0; right: 0; bottom: 58px; width: 1200px; background-color: rgba(0, 0, 0, 0.6); font-family: "Noto Sans JP", sans-serif; color: white; padding-bottom: 8px; text-align: center; } .text-label { font-size: 60px; font-weight: 500; padding-left: 0.5em; } .title-label { font-size: 28px; font-weight: 500; padding-top: 0.2em; } </style> <image xlink:href="https://.../background.png" width="1200" height="630" /> <foreignObject requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" width="1200" height="630"> <div class="contents"> <img class="book" src="https://.../thumbnail.png" /> <div class="text-contents"> <div class="text-label"> <span>読みます!!</span> </div> <div class="title-label"> <span>リーダブルコード</span> </div> <div class="title-label"> <span>ダスティン・ボズウェル/トレバー・フォシェ</span> </div> </div> </div> </foreignObject> </svg>
SVG+外部URLの画像
- SVGには文字とかだけにして、画像はファイル読み込みで対応
- カスタムフォントが使えない...のNG
- GAEかlamdaだと、fontconfigに関する環境変数を設定できるが、Cloud Functionでは無理そう
- fonts.confを設定してもダメだった...
import axios from "axios"; import sharp from "sharp"; // 埋め込む画像のURL const bookURL = "https://.../thumbnail.png"; const bookBuffer = await axios.get(bookURL, { responseType: "arraybuffer" }); // URLから取得した画像を加工(リサイズ) const bookImage = await sharp(bookBuffer.data) .resize(520, 454, { position: "top" }) .toBuffer(); // sharpで結合 await sharp("./background.png") // 背景画像を読み込み .composite([ { input: bookImage, gravity: "south" }, 本の画像を書き出し { input: "./input.svg" } // SVGの書き出し ]) .png() .toFile(tempFile);
<?xml version="1.0"?> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewbox="0 0 1200 630" width="1200" height="630"> <defs> <!-- Fontを外部URLで指定 --> <font-face font-family="Noto Sans JP"> <font-face-src> <font-face-uri xlink:href="https://fonts.googleapis.com/css?family=Noto+Sans+JP:400,700|Roboto:400,700&display=swap&subset=japanese" /> </font-face-src> </font-face> <!-- フォントをローカルファイルで指定 --> <font-face font-family="Noto Sans JP" font-weight="500"> <font-face-src> <font-face-uri xlink:href="./font/NotoSansJP-Medium.otf"> <font-face-format string="opentype"/> </font-face-uri> </font-face-src> </font-face> <font-face font-family="Noto Sans JP" font-weight="700"> <font-face-src> <font-face-uri xlink:href="./font/NotoSansJP-Bold.otf"> <font-face-format string="opentype"/> </font-face-uri> </font-face-src> </font-face> <!-- ローカルファイルを@font-faceで指定 --> <style type="text/css"> @font-face { font-family: 'Noto Sans JP'; font-style: normal; font-weight: 500; src: url('./font/NotoSansJP-Medium.otf') format("opentype"); } @font-face { font-family: 'Noto Sans JP'; font-style: normal; font-weight: 700; src: url('./font/NotoSansJP-Bold.otf') format("opentype"); } </style> </defs> <rect x="0" y="530" width="1200" height="100" fill="#000000" fill-opacity="0.6" /> <text x="610" y="156" font-size="60" fill="#FFFFFF" text-anchor="middle"> <tspan font-weight="700">読みます!!</tspan> </text> <text x="600" y="574" font-size="28" fill="#FFFFFF" text-anchor="middle"> <tspan font-weight="700">リーダブルコード</tspan> </text> <text x="600" y="610" font-size="24" fill="#FFFFFF" text-anchor="middle"> <tspan font-weight="500">ダスティン・ボズウェル/トレバー・フォシェ</tspan> </text> </svg>
SVG+ImageMagick
- 画像の加工や合成はsharpを使い、文字の書き出しだけImageMagickを使う
- 画像の加工・合成部分は、上の「SVG+外部URLの画像」と同じ感じ
- ImageMagickで文字を書くのは以下の記事を参照
node-canvas+SVG
- 同じくダメ。imageタグの外部URLがNG+foreignObject未対応...
- Automattic/node-canvas: Node canvas is a Cairo backed Canvas implementation for NodeJS.