17
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Railsの画像まわりのライブラリについて整理する(ActiveStorage, ImageMagick, ImageProcessing,,,)

Last updated at Posted at 2022-05-17

はじめに

Railsで画像を扱う方法はいくつかパターンがあり、それぞれ利用するGemやライブラリが違います。
ActiveStorage、CarrierWave、Shrine、refile、ImageMagick、ImageProcessing、MiniMagick、RMagick、libvips。。。
この辺の用語を自分の中で整理したいと思います。

環境
Mac OS Catalina 10.15.7
ruby 2.6.5
Rails 6.0.5
bundler 2.3.13
node 12.16.1
yarn 1.22.18
webpack-dev-server 3.11.2

今はActiveStorageというものがRailsに元から入っているので、これを使うことは前提とします。
必ずしもActiveStorageを使うべきということはないと思います。
例えばバリデーション機能がないことや、エラー時のキャッシュの仕組みがありません。(2022/5現在)
現時点でバリデーションをするためにはactive_storage_validationsやactivestorage-validator等のgemを導入することになるでしょう。

ActiveStorageは以下のコマンドと、has_one_attached等の設定によってすぐに画像投稿機能が出来上がります。ただshow画面でvariantメソッドを使ったサムネイル画像の表示など、画像処理機能を活用するには、これだけだと足りません。

## そのRailsアプリでActiveStorageを使えるようにする
bin/rails active_storage:install

まず結論

整理した結果、このあたりのパターンが一般的かなと思っています。

  • ActiveStorageが、MiniMagickを使ってImageMagickの画像処理機能を使う
  • ActiveStorageが、ImageProcessingにあるコマンドならImageProcessing経由で、
    無いならMiniMagick経由でImageMagickの画像処理機能を使う
  • ActiveStorageが、ImageProcessing経由でVipsを使ってlibvipsの画像処理機能を使う

ここからはどのように整理したのかを忘れないように書いておきます。

ImageMagickや、libvipsは、画像処理ソフトウェア

ActiveStorageの画像処理機能を十分に活用するには、ImageMagickかlibvipsが必要
ImageMagickもlibvipsもGemではないので、開発環境では以下のどちらかが必要
brew install imagemagick
brew install vips

情報の豊富なImageMagickか、高速なlibvipsか、選びましょう。
libvipsは、ImageMagickより画像形式のサポートが少ないが、メモリ効率や速度が良いです。
libvipsを使うのに、installするのはvipsなのが、少しわかりづらいかもしれません。

ImageMagickは、libvipsに比べて知名度が高く普及も進んでいます。しかしlibvipsは10倍高速かつメモリ消費も1/10です。JPEGファイルの場合、libjpeg-devをlibjpeg-turbo-devに置き換えると2〜7倍高速になります。 (Railsガイドより引用(Rails7.0版))

ImageProcessingは、MiniMagickを内包した上位互換のGem

  • MiniMagickは、ImageMagickをRailsで扱えるようにしてくれるGem
  • ImageProcessingは、MiniMagickで提供できない画像サイズを調整する機能があり、ImageProcessingのgemを入れると、MiniMagickのgemも一緒にインストールされる
Gemfile
gem "image_processing", ">= 1.2"
Gemfile.lock

image_processing (1.12.2)
  mini_magick (>= 4.9.5, < 5)
  ruby-vips (>= 2.0.17, < 3)

  • ちなみにRMagickは、ImageMagickをrubyで扱えるようにするGemで、MiniMagickと似たようなもの

RailsのデフォルトはMiniMagick経由でImageMagickを使う設定である

  • Active StorageのデフォルトのvariantプロセッサはMiniMagickだが、Vipsを指定することもできる
  • つまり、variantプロセッサをVipsにして、Vips経由でlibvipsを使うこともできる

ImageMagickではなくlibvipsを使う設定にするには

Vipsをinstallし、バリアントプロセッサをVipsにする

brew install vips #ローカル環境の場合、Macにinstallする
config/application.rb
config.active_storage.variant_processor = :vips

VipsとMiniMagickは互換性が高いので、MiniMagickを使うのと大体同じように使える。
なのでMiniMagickを使ったコードサンプルは参考になる。
ただし、たまに書き方を変更しないといけない場合があります。
Railsガイド7.0に、一例が載っています。

Gemごとのサムネイル生成機能の違い

CarrierWave: アップロードのタイミングで生成
Shrine: アップロード後に非同期で生成可能
ActiveStorage: 画像URLにアクセスしたタイミングで生成

ローカルで試してみる

rails new sample_app
cd sample_app
bin/rails active_storage:install
bin/rails g scaffold user name image:attachment
bin/rails db:migrate
bin/rails s

この状態でも、User作成画面で画像の投稿は可能です。

bin/rails g scaffold user name image:attachment
↑rails6.0以降で使えるこのようなモデル生成コマンドによって、
has_one_attached :imageの記述を勝手にしてくれます。

この状態だと、variantを使って、サイズ指定した画像を表示することはできません。
ImageMagickか、libvipsを使う必要がありますが、今回はlibvipsでやってみます。

Gemfile
gem "image_processing", ">= 1.2"
bundle install

brew install vips #ローカル環境の場合、Macにinstallする
config/application.rb
config.active_storage.variant_processor = :vips
app/views/users/show.html.erb
<%= image_tag @user.image.variant(resize_to_limit: [100, 100]) %>

これで、show画面でサムネイル画像を表示することができました。
libvipsを使っており、ImageMagickは使っていません。

おまけ(ActiveStorageの便利な機能)

ActiveStorageはダイレクトアップロード機能に対応しています。
アプリケーションサーバーを経由せずに直接クラウドストレージにアップロードできるというものです。
サーバーへの負担は減り、アップロード時間は速くなります。

Gemを使ってActiveStorageにバリデーション機能を持たせた場合に、
ダイレクトアップロード機能を使うと、不完全なバリデーションになってしまいます。
不完全なバリデーションを許容するか、非同期でバリデーションを実行するか。。

例) jpgのみ許可するバリデーションを作った。通常であれば本来ファイルの中身を見てjpgかどうか判断するが、
ダイレクトアップロード機能を使っている場合、ファイル名の拡張子のみを見て判断されてしまう。

ローカルで試してみます。

Rails6.0だと、これが記述してあります。

application.js
require("@rails/activestorage").start()

この状態で、

_form.html.erb
<!-- has_many_attached :imageの設定済みです --><div class="field">
    <%= form.label :images %>
    <%= form.file_field :images, multiple: true, direct_upload: true %>
  </div>

ここでは複数ファイルアップロードに対応させています。
direct_upload: trueの記述でダイレクトアップロード機能が使えます。

ダイレクトアップロードには、実行時に発火するJSイベントが用意されており、
アップロードの進捗表示なども可能です。

こんな感じのやつ↓
Active_Storage_の概要_-_Railsガイド.jpg

ソースコード(JSファイルへのコピペでOK)
app/javascript/direct_uploads.js
addEventListener("direct-upload:initialize", event => {
  const { target, detail } = event
  const { id, file } = detail
  target.insertAdjacentHTML("beforebegin", `
    <div id="direct-upload-${id}" class="direct-upload direct-upload--pending">
      <div id="direct-upload-progress-${id}" class="direct-upload__progress" style="width: 0%"></div>
      <span class="direct-upload__filename"></span>
    </div>
  `)
  target.previousElementSibling.querySelector(`.direct-upload__filename`).textContent = file.name
})

addEventListener("direct-upload:start", event => {
  const { id } = event.detail
  const element = document.getElementById(`direct-upload-${id}`)
  element.classList.remove("direct-upload--pending")
})

addEventListener("direct-upload:progress", event => {
  const { id, progress } = event.detail
  const progressElement = document.getElementById(`direct-upload-progress-${id}`)
  progressElement.style.width = `${progress}%`
})

addEventListener("direct-upload:error", event => {
  event.preventDefault()
  const { id, error } = event.detail
  const element = document.getElementById(`direct-upload-${id}`)
  element.classList.add("direct-upload--error")
  element.setAttribute("title", error)
})

addEventListener("direct-upload:end", event => {
  const { id } = event.detail
  const element = document.getElementById(`direct-upload-${id}`)
  element.classList.add("direct-upload--complete")
})

app/javascript/packs/application.js
require("@rails/ujs").start()
...
...
require("direct_uploads")

おわりに

libvipsを使って、ローカルではしっかり動いてくれました。
気になることとしてはこの辺りです。

  • より高度なことをする場合、libvipsだと情報が少なくて困るかも
  • 本番環境でもしっかり動くのか

個人開発アプリにlibvipsを使ってみて、本番で動かしてみたいと思っています。
その時の気づき等があったら、また更新したいと思います。

参考にさせていただいた書籍

パーフェクトRubyonRails 増補改訂版

17
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?