Goでheadless browserを用いた動的画像生成
当ポストはGo Advent Calendar 2019の5日目の記事です。基本ポエムを書いていて技術記事を書く感覚が鈍っていますが頑張ります。
今回はGoでheadless browser(Chrome)を用いた、動的な画像生成を行うというテーマで書きたいと思います。
なぜ画像生成したいのか?
人はなぜ画像を生成したくなるのでしょうか?僕は漫然と画像を生成するのは、時間に限りがある身としてはよくないな、と思います。ということで理由が必要なのですが、最たる例はOGP画像の生成とかじゃないでしょうか?
あるいは任意のLGTM画像を生成したくて、文字を画像にかぶせたり、インスタグラムVer1.1みたいなサービスをローンチしたい時に、フィルター加工を実装したかったりするかもしれません。
Goで画像生成するには?
Goで画像生成する方法に関してはこの1、2年でpo3rinさんや僕がしばしば登壇して解説を残しておいたので、そちらをご覧ください。
スライドに丸投げするのもアレなので、簡単に解説しますと、Goには標準で備わっているimageパッケージというものがございまして、他にもモザイク加工などがしやすくなるラッパーパッケージがいくつか存在しています。
ピクセル単位での色情報の取得や変更、文字の描画なども簡単なため、imagemagickなどがなくても画像加工ができるのがGoのiamgeパッケージのいいところです。
Goのimageパッケージの課題
Goのimageパッケージを使っていて、自分は少なくとも2つほど課題を感じます。
1. 対応フォントの少なさ
Goの利用者としてはimageパッケージ、というよりそれを取り巻くfontパッケージにやや不足感を覚えます。バグではないのですが、先ほど書いたように文字の描画をする場合、描画するfontの形式はtruetypeフォントしか対応されていません。テキストの描画用構造体(font.Drawer)に渡せるまともなfont parserが実装されているのが、truetypeパッケージくらいしかない、というのが現状です。
これはなかなか辛くて、ヒラギノなどのフォントが使えないので、運用中のサービスで使っているフォントに合わせた画像コンテンツ生成が、言語の標準機能起因でできなくなってしまいます。
2. レイアウト調整の柔軟性
当然ですが、画像合成を標準パッケージで行う場合、どこに何を表示するかかはx,y座標を入力することで調整しなくてはいけません。画像の理想形から逆算してパラメーター調整を細かくしないといけないのはなかなか至難の技かと思います。
また、border-radius: 4pxくらいの角丸を表現したりする場合は、角を削りたい画像と同じ大きさの、角だけ削れた透明の画像を重ね合わせることで描画する必要があります。画像の加工のために準備しなければならないリソースがやや多い、ということですね。
headless browserによる画像生成
標準のimageパッケージの他に画像をGoで生成する方法はないのでしょうか?ありますね。Nodeの実装とかだとよく出てくるのですが、ブラウザ起動からのスクリーンショット撮影で画像を生成する方法です。SeleniumやPuppetterをご利用になったことがある方はイメージつきやすいのではないでしょうか。
HTMLとして画像の元となるページを用意し、指定のDOM情報だけを画像としてスクリーンショット保存する方法を取れば良い、ということです。
この方法であればブラウザが対応するWebfontを利用したり、角丸などの細かいデザインもCSSで表現することができ、これまでの方法よりかなり楽に画像が自動生成できるというわけです。(もちろんブラウザのエミュレーターを一瞬起動するという点でハード面への負荷が増すことになりますが、そこまでおっかないことをするわけではありません)
こちらの方法は、Go Conference'19 Summer in Fukuokaで、codehexさんがchromedpというGoのchromeのドライバーパッケージを使っていたのを見て、「いけるんじゃないかな?」と思っていたら、実際10分くらいで撮影まで実装できました。意外とさくっとできてよかったです。
具体的な画像生成の実装
処理の流れは、
1. 静的コンテンツ配信サーバーを起動する
2. chromedpでブラウザを起動し、ローカルホストの指定DOMの画像を撮影
3. 保存まで終わったらシグナルを伝達し、サーバーをシャットダウン
という3手順で組んでみました。結構あっさりしてるのではないかなと思います。ページ全体を撮影すると余白が入るので、id指定で範囲を限定するようにしました。
chromedpパッケージ自体は、スクショの他にもクリック、ファイルアップロード、テキスト抽出などができるため、captchaなどでガードされてないサイトのスクレイピングなどにも使えます。負荷が少ない範囲で色々試してみてもらえると嬉しいです。