こんにちは、rightgo09 です。今回は libvips + WebP での簡単な検証結果を確認します。
libvips とは
高速かつ低メモリな画像変換ライブラリです。
Rails の ActiveStorage の画像リサイズ(自分で指定する必要ありますが)や、AWS Lambda に標準で搭載されるなど、いろいろな場面で ImageMagick ではなく libvips が使用されるようになっているようです。
ref. Why is libvips quick · libvips/libvips Wiki · GitHub
WebP とは
- An image format for the Web | WebP | Google Developers
- WebP - Wikipedia
- WebP image format | Can I use... Support tables for HTML5, CSS3, etc
比較的新しい画像フォーマットです。
IE のサポートが 2022 年 6 月に終了したため、ほぼすべてのブラウザで対応済みとなりました。以前よりは安心して使用可能な状況と言えそうです。
AWS ブログによる紹介記事でも採用されている
こちらのブログでは、libvips で WebP に変換する内容が紹介されています。一例ではありますが、AWS としてもこの組み合わせは価値があるとみなしているのかもしれません。
※ Node.js の sharp ライブラリが libvips を使用している
※ リクエストの Accept ヘッダが許していれば WebP に変換している
sharp
上述の AWS ブログでも採用されている、libvips を使用した Node.js のライブラリ「sharp」では
Resizing an image is typically 4x-5x faster than using the quickest ImageMagick and GraphicsMagick settings due to its use of libvips.
とあります。すごい。
簡単に検証してみた
事前に用意できない、例えばユーザがアップロードした画像を動的にサムネイルにして表示させる場合を想定し、以下を確認します。
- 処理時間
- ファイルサイズ
- 画質
JPEG 画像を変換する
1枚目の画像 (JPEG) (1798x2398) (2,051,477 bytes)
2枚目の画像 (JPEG) (4032x3024) (3,401,442 bytes)
変換する
- 1枚目の画像 → 500 x 667 ( 28% )
- 2枚目の画像 → 504 x 378 ( 12.5% )
結果
quality を 30 にすると、JPEG ではガビガビしたところが出てきましたが、WebP ではまだまともなサムネイルのままのように見えます。
まとめと所感
- クオリティを低くしたとき、ともに処理時間は短くなる傾向にあり、ファイルサイズは減る
- クオリティを低くしたとき、ファイルサイズが同じくらいなら JPEG よりも WebP の方がきれいに見える
- クオリティの指定をしないとき、libvips の処理時間は ImageMagick よりも早いしファイルサイズは小さいし、見た目で違いはわからない
- クオリティの指定をしないとき、WebP はファイルサイズが小さくなるし処理速度も早い
処理速度と見た目の良さを考慮すると、libvips で WebP を使っていく(クオリティは要模索)のがユーザの満足面、インフラのコスト面ともに良さそうなのかな、という簡単な検証結果でした。
Web サービスは高速化の時代なので、いかに CDN にキャッシュを載せていくか、CDN からのラストワンマイルでいかに運ぶ量を減らすか、ということで、今回のようないかに小さいサイズで満足できる画質を提供できるかといった模索は継続して必要のようです。
計測コード
環境は MacBook Pro (2018) Intel Core i7 2.2GHz 16GB メモリ
ImageMagick の方は Ruby バインディングの RMagick で、libvips の方は Node.js の sharp で変換した際の処理時間を内部で計測しました。Node.js の ImageMagick のライブラリがうまく使えず、Ruby にて ImageMagick の計測を行いました。言語による違いが処理速度に含まれていますが、画像変換の時間に比べれば小さいものだろうと判断して今回の調査として使用しました。
生成画像のファイルサイズが小さいほど最後の書き込み時間が有利になりますが、それも込みで計測しています。
また sharp での JPEG → JPEG への変換に mozjpeg を使用すると、処理時間は 2 倍程度長くなるものの生成画像のサイズが何割か小さくなる、という結果になりました。
ImageMagick
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC) image = Magick::Image.read(src_path).first image.format = format if format == "WEBP" && quality != "default" image.define("webp:emulate-jpeg-size", true) end image.resize!(w, h) if quality == "default" image.write(dest_path) else image.write(dest_path) { |i| i.quality = quality } end ending = Process.clock_gettime(Process::CLOCK_MONOTONIC) elapsed = (ending - starting) * 1000 size = Magick::Image.read(dest_path).first.filesize puts "#{src_path}\tRMagick\t#{format}(Quality #{quality})\t#{elapsed.round(5)} ms\t#{size} bytes"
libvips
const hrstart = process.hrtime(); let info; if (format === "jpeg") { if (quality === "default") { info = await sharp(src_path).resize(w, h).toFile(dest_path); // info = await sharp(src_path).jpeg({ mozjpeg: true }).resize(w, h).toFile(dest_path); } else { info = await sharp(src_path).jpeg({ quality: quality }).resize(w, h).toFile(dest_path); // info = await sharp(src_path).jpeg({ mozjpeg: true, quality: quality }).resize(w, h).toFile(dest_path); } } else if (format === "webp") { if (quality === "default") { info = await sharp(src_path).toFormat(format).resize(w, h).toFile(dest_path); } else { info = await sharp(src_path).toFormat(format).webp({ quality: quality }).resize(w, h).toFile(dest_path); } } const hrend = process.hrtime(hrstart); const ms = hrend[1] / 1000000; console.info(`${src_path}\tSharp\t${format}(Quality ${quality})\t${ms} ms\t${info['size']} bytes`);