1枚ずつ違うダミー画像を ruby-vips で作って CarrierWave でアップロード!
ラブグラフのエンジニア、横江( @yokoe24 )です!
以前の記事で、 CarrierWave 用のカラムにデータを設定する方法を書きました。
その記事ではカンタンにするため、以下のようなコードですべて同じ画像を設定しました。
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