こんにちは。
みなさんは画像処理のgemはどれを使っていますか?
昔だとrmagick一強な印象がありましたが、最近だとimage_processingをRailsガイドも推奨するようになりました。
image_processingは、libvipsやimage magickという画像処理ライブラリをRubyで使えるようにしたものです。
また、image_processing内で使用するライブラリは選択可能で、libvipsかimage magickを選べます。
今回は、RailsでOGP画像を作成する方法として、rmagickからimage_processing内のlibvipsに乗り換える方法を紹介します。
単純にするために、Postモデルにtitleというカラムがあり、それを使用したOGP画像を作成するメソッドを#create_image
とします。
1. 既存のOGP画像作成
# Dockerfile
FROM ruby:3.2.1-slim
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev vim git imagemagick
# 以下省略
# Gemfile
gem 'mini_magick'
# app/models/post.rb
class Post < ApplicationRecord
require 'mini_magick'
OGP_BASE_IMAGE_PATH = './public/images/posts/ogp_base.png'.freeze
OGP_GRAVITY = 'center'.freeze
OGP_TEXT_POSITION = '0,0'.freeze
OGP_FONT_PATH = './public/fonts/NotoSansJP-Bold.otf'.freeze
OGP_FONT_SIZE = 70
OGP_INDENTION_COUNT = 15
OGP_ROW_LIMIT = 8
OGP_TEXT_COLOR = 'white'.freeze
def set_ogp_image
image = create_image
ogp_image.attach(io: StringIO.open(image.to_blob), filename: 'ogp.png')
end
private
def create_image
text = prepare_text(title.truncate(OGP_TRUNCATE_LENGTH))
image = MiniMagick::Image.open(OGP_BASE_IMAGE_PATH)
image.combine_options do |config|
config.font OGP_FONT_PATH
config.fill OGP_TEXT_COLOR
config.gravity OGP_GRAVITY
config.pointsize OGP_FONT_SIZE
config.draw "text #{OGP_TEXT_POSITION} '#{text}'"
end
image
end
end
これで、#set_ogp_image
を呼び出すと、OGP画像が作成され、ActiveStorageで管理されるようになります。
2. image_processingを使ったOGP画像作成
それでは、ここからはimage_processingを使ったOGP画像作成に乗り換えていきます。
# Dockerfile
FROM ruby:3.2.1-slim
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev vim git imagemagick libvips # libvipsを追加
# 以下省略
# Gemfile
# mini_magickを削除し、image_processingを追加
gem 'image_processing'
# app/models/post.rb
class Post < ApplicationRecord
require 'image_processing/vips'
OGP_BASE_IMAGE_PATH = './public/images/posts/ogp_base.png'.freeze
OGP_FONT = 'Noto Sans JP 32'.freeze
OGP_INDENTION_COUNT = 16
OGP_TEXT_COLOR = [255, 255, 255].freeze
OGP_TRUNCATE_LENGTH = 60
OGP_ROW_LIMIT = 8
OGP_DPI = 140
def set_ogp_image
image = create_image
ogp_image.attach(io: StringIO.open(image.write_to_buffer('.png')), filename: 'ogp.png')
end
private
def create_image
base_image = Vips::Image.new_from_file(OGP_BASE_IMAGE_PATH)
text_mask = Vips::Image.text(text, fontfile: OGP_FONT_PATH, font: OGP_FONT, dpi: OGP_DPI,
width: base_image.width.to_i - 100, height: base_image.height.to_i - 100, align: 'centre')
rgb = text_mask.new_from_image(OGP_TEXT_COLOR).copy(interpretation: 'srgb')
text_image = rgb.bandjoin(text_mask)
x = (base_image.width.to_i - text_image.width.to_i) / 2
y = (base_image.height.to_i - text_image.height.to_i) / 2
base_image.composite(text_image, 'over', x:, y:)
end
end
これで、先ほどと同様#set_ogp_image
を呼び出すと、OGP画像が作成され、ActiveStorageで管理されるようになります。
3. 変更点の解説
まず触ってみた感想として、rmagickかなり親切だな…という感じでした。
そもそも、rmagickでは#draw
で文字列を描画することができるのですが、libvipsでは.text
で文字列から画像を作成し、それを重ね合わせるという方法で文字列を描画する必要があります。
この辺りのドキュメントも少なく、drawに変わるメソッドがないか探していましたが、見つからず、この方法で実装しました。
https://www.rubydoc.info/gems/ruby-vips/Vips/Image#text-class_method
そのほかにも、rmagickではたとえばOGP_GRAVITY
で指定しているように、中央揃えの場合はcenter
などのように文字列で指定できるのでわかりやすいですが、libvipsではx, yの座標で指定する必要があります。
そのため、#create_image
内で、text_image
のx, yの座標を計算しています。
なんとなくrmagickの方が全体的なドキュメントは豊富な印象ですが、libvipsの方が高速で、メモリを圧迫しないというメリットがある(らしい)ので、今後も使っていきたいと思います。
(ちゃんと計測しろよという話でもあるので、また別の機会に試してみます)
今回はこのあたりで。