🧑‍🎨

1枚ずつ違うダミー画像を ruby-vips で作って CarrierWave でアップロード!

2024/09/09に公開

ラブグラフのエンジニア、横江( @yokoe24 )です!

以前の記事で、 CarrierWave 用のカラムにデータを設定する方法を書きました。

https://zenn.dev/lovegraph/articles/3484277f7a377d

その記事ではカンタンにするため、以下のようなコードですべて同じ画像を設定しました。

file = File.open("app/assets/images/dummy_avatar.png")

User.first(100).pluck(:id).each do |user_id|
  UserSetting.create!(user_id: user_id, avatar: file)
end

しかし、それでは 同じ画像が並んでわかりにくい! という問題があります。
1枚1枚異なる画像を設定できないか 、この記事ではやってみました!

libvips のインストール

libvips というのは、
有名な ImageMagick よりもメモリ消費量が少なく、処理が高速だとして注目されている、画像処理ライブラリです。

まずはその libvips のインストールをおこないましょう。

Rails を Docker コンテナ上で動かしているものとして、
Docker イメージには ruby:3.3.5-bookworm などの Debian 系イメージを使用しているものとします。

Docker コンテナに入ったあとで、
以下のコマンドを実行し、まずは libvips をインストール します。

apt update && apt install -y libvips42

ruby-vips のインストール

次に、libvips を Ruby で動かすための gem である
ruby-vips をインストールしましょう。

Gemfile に ruby-vips について記載したあと、
Docker コンテナに入っている状態で bundle install をおこなってください。

ruby-vips のインストールが終わったら、
bundle exec rails c などで Rails コンソールを立ち上げます。

画像を生成してみる

さて、いよいよ libvips を使って画像生成をしてみましょう!

require "vips"

width = 400
height = 400

background_color = [173, 216, 230] # パステルブルー

# ベース画像の作成
image = Vips::Image.black(width, height).add(background_color)

# 「42」と書かれているテキスト画像を作成
text_image = Vips::Image.text("No. 42", dpi: 300)

# テキスト画像を中央に配置するための計算
pos_x = (image.width - text_image.width) / 2
pos_y = (image.height - text_image.height) / 2

# ベース画像にテキスト画像を合成
image = image.insert(text_image, pos_x, pos_y)

image.write_to_file("output.png")

すると、以下のような画像が出力されました!

(本当はテキストだけに色を当てたかったのですが、どうしても text_image 側に背景色が付いちゃって上手くいきませんでした……🥲)

これを使えば、ユーザーごとにアイコンを作成することが可能そうですね!

ひとりひとり違うアバターを作るスクリプト

以上のことを踏まえて、ひとりひとり違うアバター画像を設定するスクリプトファイルは以下のようになりました。

require "vips"

class CreateUserSettings
  BACKGROUND_COLORS = [
    [255, 182, 193],  # パステルピンク
    [255, 223, 186],  # パステルオレンジ
    [255, 255, 204],  # パステルイエロー
    [204, 255, 204],  # パステルグリーン
    [204, 229, 255],  # パステルブルー
    [230, 230, 250],  # ラベンダー
    [216, 191, 216],  # パステルパープル
    [255, 239, 213],  # パステルパーチメント
    [255, 235, 205],  # ライトパステルペーチ
    [255, 204, 229],  # ピンキーパステル
    [248, 187, 208],  # パステルローズ
    [173, 216, 230],  # パステルブルー
  ]

  SIZE = 800 # 画像の縦横のピクセルサイズ

  def self.exec
    User.first(100).pluck(:id).each do |user_id|
      image = generate_dummy_image(user_id)

      buffer = image.write_to_buffer(".png")

      tempfile = Tempfile.new("user_#{user_id}_avatar")
      tempfile.binmode
      tempfile.write(buffer)
      tempfile.rewind

      UserSetting.create!(user_id: user_id, avatar: tempfile)

      tempfile.close
    end
  end

  def self.generate_dummy_image(number)
    width = SIZE
    height = SIZE
    background_color = BACKGROUND_COLORS.sample

    image = Vips::Image.black(width, height).add(background_color) # ベース画像
    text_image = Vips::Image.text("No. #{number}", dpi: 300) # テキスト画像

    # テキスト画像を中央に配置するための計算
    pos_x = (image.width - text_image.width) / 2
    pos_y = (image.height - text_image.height) / 2

    # ベース画像にテキスト画像を合成
    image = image.insert(text_image, pos_x, pos_y)
  end
end

CreateUserSettings.exec

Tempfile を使っているのがポイントですねー。

Vips::Image を Tempfile へと変換することによって、
Tempfile は File クラスと実質的に同じ扱いをすることができるので、
CarrierWave のアップロード対象として指定できているわけです、

最後に、そうして作り上げた写真をいい感じにカード内に置いて並べました。

きれいですねー。
(テキストの透過処理がちゃんと実装できたらもっときれいだったのに・・・!!!)

なお、1枚ごとのファイル生成や、CarrierWave による画像アップロードによって
時間はそれなりにかかりますのでお気を付けください。

time コマンドで見たところ、私の環境では2分20秒かかっていたようです。
必要に応じて使用するようにしましょう!

ラブグラフのエンジニアブログ

Discussion