WordPressが採用したWASM画像変換ライブラリでPNG→AVIF変換の速度とファイルサイズを検証する

ブログ

WordPressがWASMの画像変換 wasm-vips を採用した

i.pngWordPress-logotype-standard.png

2026年にリリースされたWordPress 7.1では@wordpress/vipsパッケージが正式に導入され、ブラウザ上での画像圧縮・リサイズ・フォーマット変換・サムネイル生成を、libvipsをWebAssembly化した wasm-vips が担うようになりました。

  • Web Worker上で動作し、メインスレッドをブロックしない設計
  • Document-Isolation-Policyに対応

今回はwasm-vipsを使い、実際にPNG→AVIF変換がどれくらいの速度・ファイルサイズになるのかを検証しました。

あわせて、ネイティブ実装のAVIFエンコーダであるcavifとも比較します。

検証環境の実装

Vite + React + TypeScriptで検証用アプリを構築し、wasm-vipsをWeb Worker上で実行する構成にしました。UIをブロックせず、実運用に近い形で計測するためです。

実装にあたって、いくつかハマりどころがありました。

  • wasm-vipsは`SharedArrayBuffer`を使うため、開発サーバーに以下のヘッダーが必須
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
  • Viteでのバンドルには vite-plugin-wasmvite-plugin-top-level-await が必要
  • AVIF書き出しは image.avifsaveBuffer() のような専用メソッドは存在せず、汎用の image.writeToBuffer('.avif', { Q: quality }) を使うのが正解(ドキュメント上わかりにくい点)

検証1: Q値ごとの変換時間・ファイルサイズ(ブラウザ内)

800×600pxのテストPNG(約437KB)を、Q値30・50・70・90でAVIFに変換した結果です。

Q値変換時間 (ms)元サイズ (KB)AVIFサイズ (KB)圧縮率
50911.7436.65.698.7%
70963.7436.67.998.2%
901579.6436.616.596.2%

Web Worker上でも、wasm-vipsの初期化自体は150〜350ms程度で完了し、実用上問題ないレベルでした。

検証2: ネイティブcavif(rav1eエンコーダ)との比較

同じPNGサンプル(実写真ベースの3枚、約1000×1280px)を使い、Q値50〜90(10刻み)で、wasm-vipsとネイティブの`cavif`(rav1eエンコーダを使うRust製AVIFツール)を比較しました。

Q値wasm-vips サイズcavif サイズサイズ比(wv/cavif)wasm-vips 時間cavif 時間速度比
5052.6 KB37.4 KB1.41倍4,146 ms279 ms約15倍遅い
6083.9 KB49.1 KB1.71倍4,461 ms352 ms約13倍遅い
70120.4 KB65.3 KB1.84倍4,943 ms368 ms約13倍遅い
80172.2 KB84.2 KB2.05倍5,636 ms644 ms約9倍遅い
90282.8 KB165.6 KB1.71倍8,844 ms824 ms約11倍遅い

同じQ値を指定しても、wasm-vipsの方が全帯域でファイルサイズが1.4〜2倍大きく、変換時間も9〜15倍かかるという結果になりました。ここで重要なのは、「Q値」はツールごとの内部スケールであり、両者間に互換性はないという点です。同じ見た目の圧縮率を狙うなら、wasm-vipsはcavifよりだいぶ低いQ値を使う必要があります。

なぜ差が出るのか:libaom vs rav1e

AVIFは「HEIFコンテナ + AV1コーデック」という規格なので、両者とも最終的には同じAV1というコーデックにたどり着きます。しかしAV1のエンコーダ実装が異なります

エンコーダ実装言語
wasm-vipslibaom (AOM)C
cavifrav1eRust

wasm-vipsが実際にどのエンコーダを使っているかは、配布されているWASMバイナリを`strings`コマンドで調査することで確認できました。

vips-heif.wasm(AVIF/HEIF変換プラグイン)の中身にはAOMedia AV1 encoderの文字列のみが含まれており、APIの型定義上はrav1eやx265という選択肢も存在するものの、このビルドでは実際に機能するのはAOMだけでした。

libaomは「リファレンス実装」として圧縮率を追求する設計、rav1eは実用速度を重視した設計というアプローチの違いが、今回の速度・サイズ差にそのまま表れた形です。

rav1eをWASM化する道はあるか

「それならrav1eをWASM化すればいいのでは?」という疑問から、追加で調査しました。

cavifおよびrav1eはいずれもオープンソースです。

Rust製なのでwasm-packでwasm32ターゲットへのビルド自体は技術的に可能です。実際に試みたリポジトリ(saschazar21/wasm-rav1e)も存在します。しかし、作者自身が認める深刻な課題がありました。

  • 3072×2048pxの画像1枚のエンコードに約4分(デフォルト設定)
  • 4:2:0以外のクロマサブサンプリング使用時に画像が破損するバグ

rav1eのネイティブでの速さは、x86_64向けアセンブリ最適化やSIMD命令に強く依存しています。WASMではこれらの最適化の多くが使えないため、rav1eの最大の強みである「速さ」が失われてしまうと考えられます。

さらにnpm上を調査したところ、rav1eベースの実用的なWASMパッケージは実質的に存在しないことがわかりました。

パッケージ実態
rav1e(npm, v0.3.0)中身のwasmファイルがわずか150バイトのスタブ。機能しない
wasm-rav1e(GitHub)npm未公開。ローカルビルド前提
@saschazar/wasm-avif説明文にrav1e/dav1dへの言及があるが、実際に配布されているwasmバイナリの中身はlibaom
@jsquash/avif(Squoosh由来)同様に実体はlibaom

つまり、既存のWASM AVIFライブラリは軒並みlibaomベースであり、今回のwasm-vipsと同じ土俵にいることが確認できました。

まとめ

  • WordPress 7.1が採用したことからもわかる通り、wasm-vips(libaom)はブラウザ内画像変換の現実的な選択肢として十分実用に耐える
  • ただし、変換速度・圧縮率を最優先するなら、サーバーサイドでcavifやrav1e相当のネイティブ実装を使う方が合理的
  • rav1eをWASM化する試みは技術的には可能だが、現状は実用速度に届いていない。npm上にも実用的な公開パッケージは存在しない
  • 「同じQ値」でもエンコーダが異なれば単純比較はできない点に注意が必要

一覧へ戻る