libvips が作る JPEG の quality は粗い

に公開

こんにちは、st-1985 です。

弊社のサービスの一つである Message Manager では、管理画面でアップロードされた画像を保存し、LINE 用のフォーマットに変換した上で配信していますが、その配信された画像に関してユーザーから「文字がつぶれて読みにくくなった」との声を頂きました。

調査の結果、 ActiveStorage で指定している画像加工プロセッサ(libvips) の JPEG におけるデフォルトの品質(quality)が低めに設定されていることがわかりました。

この記事ではその詳細と改善策について紹介します。

libvips について

libvips は、大量の画像を処理する用途に適した高速かつ省メモリな画像処理ライブラリです。

Rails 7 以降、ActiveStorage における画像加工プロセッサのデフォルトが ImageMagick から libvips に変更されたため、弊社でもこれに従って libvips へ移行しました。

libvips は ImageMagick と比べて一部の挙動やデフォルト設定に違いがあり、その1つが本記事で扱う JPEG の quality 設定です。

quality とは?

JPEG の quality は圧縮率と画質のバランスを決めるパラメータです。

0〜100 の範囲で指定し、値が高いほど画質が良く(圧縮率は低くなりファイルサイズが大きくなる)、値が低いほど画質が粗くなります。

画像中の細かい文字や線などは、quality が低すぎるとつぶれてしまうことがあります。

libvips と ImageMagick の quality の決まり方

libvips で画像を加工した場合、元画像の quality は引き継がれず、デフォルトで 75 が設定されます。

ActiveStorage.variant_processor # => :vips

user = User.create!(name: "SocialPlus")
user.avatar.attach(io: File.open("image/example_image_quality_100.jpg"), filename: "avatar.jpg")

variant_option = { format: "jpeg" } # JPEGを強制
variant = user.avatar.variant(variant_option).processed # variant を生成

variant_file_path = variant.blob.service.send(:path_for, variant.key) # key から variant のファイルパスを取得

# 作成された画像の quality を外部コマンドを使って確認
command = "identify -verbose #{Shellwords.escape(variant_file_path)} | grep -i 'quality'"
Open3.capture3(command).first # =>   Quality: 75

一方、ImageMagick は元画像の quality を引き継ごうとし、出来なければデフォルトで 92 が設定されます。

ActiveStorage.variant_processor # => :mini_magick

(同じなので中略)

Open3.capture3(command).first # =>   Quality: 100

# PNGを指定することで quality が引き継がれないようにする
user.avatar.attach(io: File.open("image/example_image.png"), filename: "avatar.jpg")

(同じなので中略)

Open3.capture3(command).first # =>   Quality: 92

両者のデフォルト値の画像を比較したものが以下です。
左 : libvips(75)/右 : ImageMagick(92)

ぱっと見た感じでは違いはないように見えますが、拡大してみると、libvips の方がやや粗く靄がかかっているように見えることがわかります。

なおそれぞれのファイルサイズについては

ツール quality サイズ
libvips 75 64,721Byte
ImageMagick 92 188,290Byte
元画像 100 214,132Byte

と、quality の値に応じたサイズになっています。

改善内容

ImageMagick に戻すのも一つの手ですが、できる限りデフォルトのライブラリを使用したいという方針であるため、画像加工時の実装を調整することにしました。

対応については簡単で、variant メソッドを使って画像を加工する際に quality 値を指定するだけです。

variant_option = { format: "jpeg", saver: { quality: 90 } } # quality を指定
variant = user.avatar.variant(variant_option).processed 
variant_file_path = variant.blob.service.send(:path_for, variant.key)

command = "identify -verbose #{Shellwords.escape(variant_file_path)} | grep -i 'quality'"
Open3.capture3(command).first # =>   Quality: 90

quality の調整

quality の値をどれくらいに設定するかは、(古いドキュメントですが) Google の推奨値である 85 が参考になりそうです。
(この値以上の値を指定してもファイルサイズの増加の割に画質の向上は少ないとのこと)

弊社では、ユーザーから指摘のあった元画像の quality が 91 であった経緯から近い値の 90 を指定して画像を生成していますが、複数の画像サイズで一律に設定されている為、小さいものに関してはもっと低い値を設定しても問題ないかもしれません。
今後の課題になりそうです。

参考までに、本記事のサンプル画像を複数の quality 値で生成した際の各ファイルサイズを以下に示します。

quality サイズ
75 64,721Byte
80 71,526Byte
85 80,845Byte
90 125,900Byte

拡大画像で比較すると以下のようになっています。

まとめ

  • Rails7 で libvips に移行すると、加工したJPEGの quality が 75 に下がり画像が見づらくなることがある
  • 加工時に quality を指定すれば画質を改善可能
  • 用途やサイズごとに quality を調整できるとなお良し

参考になれば幸いです。

参考リンク

Social PLUS Tech Blog

Discussion