🥉

favlist『2023年今年買ってよかったもの3選』キャンペーンを支える技術〜MiniMagickを用いて動的なOGP画像を生成する〜

2023/12/19に公開

株式会社マイベストでエンジニアをしています、あまね(@isaka1022)です。

2023年12月現在、僕が所属するfavlistチームでは、「2023年買ってよかったもの3選」キャンペーンを実施しています。キャンペーン特設ページから、ご自身が2023年買ってよかったものをまとめるページを作っていただき、X上でシェアしていただくと総額155万円分の賞品が抽選で当たります。

https://my-best.com/lists/2023campaign

実際に作ったページ↓

https://my-best.com/lists/2023campaign/18

今回はこのキャンペーンを実施するに当たり、MiniMagickを用いて動的な画像生成機能を入れたので、その話を紹介できればと思います。

やりたかったこと

今回はユーザーの方々に対して、2023年を振り返ってもらい、フォームから買ってよかった商品をタグやコメントと合わせて登録してもらうことでページを作成してもらうことができるプロダクトを提供しました。

画像生成機能の導入の動機と技術選定

これを実現する上で、自分が買ったものを簡単に他の人やSNS上にシェアしやすいように、ページごとにアイキャッチとOGP画像を動的に作成したいという要求があったので、そこをどのように解決したかを紹介できればと思います。

まずはこの動的な画像生成をフロントエンド側で行うか、バックエンド側で行うかというところから考え始めました。
つまり、ユーザーがフォームから商品を登録したタイミングでフロントエンド側で画像を作成してしまい、作成後の画像をバックエンド側に対して保存するリクエストとともに送るという方法と、フロントエンド側からは登録された商品、コメント、タグの情報だけをバックエンド側に送り、バックエンド側で保存処理を行うというものになります。

マイベストでは基本的にバックエンド側にビジネスロジックや処理をなるべく寄せるという開発方針がありますが、一応この両軸を検討した結果、

  • フロントエンド側で今回の用途に使えるようなライブラリがなかった(ユーザーが画像をトリミングしたり加工したりできるようにするものはあったが Too muchまたは私達運営がやりたいことを満たせなかった)
  • フロントエンド側での処理をなるべく軽くし、ユーザーの閲覧→ページの作成までの流れの中で処理が止まり登録した情報が失われてしまう、という体験をなくす

という大きな2つの理由から、バックエンド側で処理を実行することにしました。

favlistのバックエンドはmybestと同様にRuby on Railsを使用しているため、画像処理ライブラリをいくつか見てみましたが、既にgemが導入されていたMiniMagickというgemを使ってImageMagickを使用することで画像生成を行うことにしました。

画像生成ライブラリの概要

MiniMagickは画像処理ライブラリImageMagickをラップしたgemです。RMagickというgemもありますが、それよりも使用するメモリが少なく軽量化されたものになっています。

https://github.com/minimagick/minimagick

ImageMagickでは、さまざまな画像フォーマットをサポートし、画像の作成、変換、編集などを行うことです。マンドラインツールとしても、APIとしても利用可能です。例えば、リサイズ、回転、色調整、テキストの追加など、幅広い画像処理が可能です。

https://imagemagick.org/index.php

今回のキャンペーンで使いたかった機能はこのうち、

  • 背景画像を指定する
  • 背景画像に対して、画像を重ね合わせる
  • タイトルや商品説明文を埋め込む

という3つを使用したかったので、MiniMagickを用いてそれぞれをどのように実現したかを紹介します。

MiniMagickを用いてOGP画像を動的に作成する

最終的に作成したい画像はこのような画像になります。

背景画像を準備する

予めデザイナーさんと打ち合わせをしておき、背景画像や、その上に重ねるべき商品画像、タグ画像、そしてコメントの位置を調整しておきます。

基本的にMiniMagickを使用する際にはImageMagickのドキュメントを読みながら、使用するオプションを確認していきます。

draw, crop, compositeの処理を行う際には位置調整の際には、どこを起点としてどのように重ねるかを合わせる基準として、ImageMagickではgravityというな概念があるので、これを確認しておきます。
NorthWest, North, NorthEast, West, Center, East, SouthWest, South, SouthEastを確認してそれぞれのコマンドでどこが起点になるかを確認します。

https://imagemagick.org/script/command-line-options.php#gravity

まずは背景画像を読み込みを行います。

base_image = MiniMagick::Image.open(BASE_IMAGE_PATH)

画像を重ね合わせる

画像を重ね合わせるためには、2つの処理が必要です。

まずは、重ね合わせる方の画像を読み出し、その画像データの大きさを調整します。

overlay_image = MiniMagick::Image.open(OVERLAY_IMAGE_PATH)
MiniMagick::Tool::Convert.new do |img|
  img << overlay_image.path
  img.resize(SIZE)
  img << overlay_image.path
end

こうすることで、重ねたい画像のパスに、サイズ調整済の画像で置き換える処理が実行できました。

次に、その画像を重ね合わせていきます。

result = base_image.composite(overlay_image) do |config|
  config.compose 'Over'
  config.geometry OVERLAY_POSITION
end

重ね合わせた結果はresultに格納されるので、これに対して再度処理を加えていきます。

文字を重ね合わせる

画像の上から文字を入力するには、drawメソッドを使います。
この部分では、ベースとなる画像を開き、タイトルやコメントなどのテキストを追加しています。
その際に、POINTSIZEでフォントサイズ、kerningで文字の間隔の調整を行っています。

result = base_image.combine_options do |config|
  config.font 'YuGothic-Bold'
  config.pointsize POINTSIZE
  config.kerning KERNING
  config.draw "text #{TITLE_POSITION} '#{title}'"
end

今回はフォントでは游ゴシック体を使用したのですが、ハマったポイントがあります。
Dockerを使っての開発だったのですが、作成するコンテナにフォントがインストールされていないと、正しく表示されなかったのです。
事前にフォントファイルの準備と、Dockerへのインストールが必要になります。

RUN mkdir -p /usr/share/fonts
COPY ./assets/fonts /usr/share/fonts

これらを繰り返し調整しながら動的な画像作成を行っていきます。

結果

結果として、ユーザーのみなさんが自分が買ってよかったものと、コメントとタグを付けた画像を作成することができるようになりました。
この機能を使って、色々な方々が実際に画像付きのページを作成してくれました!

https://my-best.com/lists/2023campaign/193

https://my-best.com/lists/2023campaign/44

https://my-best.com/lists/2023campaign/7

また、画像や機能についてのコメントもいただけて嬉しい限りです。
一般の人々向けにプロダクトを開発するやりがいや醍醐味として、こうやって自分が試行錯誤して作った機能が沢山の人に使ってもらえたり、反響をもらえるのはとても大きいなと感じています。

https://x.com/Bdash_88/status/1731773220024713539?s=20

https://x.com/sotaronishida/status/1731605520170303848?s=20

このキャンペーン、2023年の12月が終わるまで実施していますので、ぜひみなさんの買ってよかった者たちも紹介していただけると嬉しいなと思います!

https://my-best.com/lists/2023campaign

参考サイト

https://tech.mof-mof.co.jp/blog/rails-minimagick-image-composition/

https://qiita.com/naka-j/items/032ebbbe00901979309e

Discussion