こんにちは。GMOメイクショップの黒木です。
makeshop の新管理画面では WebP 形式の画像をアップロードできるようになりました。
WebP をアップロードできるようにした背景には、様々な課題や試行錯誤がありました。今回は、その背景やどのように課題を乗り越えたのかについてご紹介します。
WebP を登録するための要件
以前から、WebP をアップロードして以下のようなことを実現したい という熱い要望が寄せられていました。
- 画像を軽量化し、サイトの表示速度を向上させたい
- animated WebP に対応し、アニメーションを利用できるようにしたい
WebP のアップロードを実現するには、makeshop の仕様上、以下の要件をクリアする必要がありました。
- 商品画像は最大 20MB までアップロード可能。アップロードされた画像は 200KB 以下に圧縮され、その後、中画像・小画像としてリサイズされる。
これらの要件を踏まえ、以下の2つを満たせるように実装を進めました。
- animated WebP に対応する
- WebP を圧縮できるようにする
課題① 圧縮できない
Go の標準パッケージには WebP を圧縮できるものがないため、WebP に対応しているライブラリを探すことにしました。
bimg で解決する
まずは調査する中で見つけた bimg という Go の画像処理ライブラリを試してみることにしました。
以下のようなコードで、WebP に対応できるか、また要件を満たせるかを確認しました。
要件 | 検証結果 | |
---|---|---|
❌ | animated WebP に対応すること | アニメーションが失われて、静止画として出力された。 |
⭕️ | WebP を圧縮できること | 500×500 に圧縮できた。 |
bimg の issue でも報告されているように、animated WebP の圧縮はサポートされていませんでした。そのため、別のライブラリを検討することにしました。
https://github.com/h2non/bimg/issues/441
package main import ( "log" "os" "github.com/h2non/bimg" ) func main() { buffer, err := os.ReadFile("input.webp") if err != nil { log.Fatal(err) } resized, err := bimg.NewImage(buffer).Process(bimg.Options{Type: bimg.WEBP, Height: 500, Width: 500}) if err != nil { log.Fatal(err) } if err := bimg.Write("output.webp", resized); err != nil { log.Fatal(err) } }
govips で解決する
同じく Go で書かれた govips という画像処理ライブラリを試してみました。
https://github.com/davidbyttow/govips
以下のようなコードで、 WebP に対応できるか、また要件を満たせるかを確認しました。
要件 | 検証結果 | |
---|---|---|
⭕️ | animated WebP に対応すること | アニメーションが失われず圧縮できた。 |
⭕️ | WebP を圧縮できること | 500×500 に圧縮できた。 |
govips を使用すると、animated WebP のアニメーションを保持したまま圧縮できることが確認できました。
package main import ( "log" "os" "github.com/davidbyttow/govips/v2/vips" ) func main() { vips.Startup(nil) defer vips.Shutdown() importParams := vips.NewImportParams() importParams.NumPages.Set(-1) img, err := vips.LoadImageFromFile("input.webp", importParams) if err != nil { log.Fatalln(err) } if err := img.Thumbnail(500, 500, vips.InterestingAll); err != nil { log.Fatalln(err) } b, _, err := img.ExportNative() if err != nil { log.Fatalln(err) } if err := os.WriteFile("output.webp", b, 0644); err != nil { log.Fatalln(err) } }
課題② パフォーマンスが悪い
govips を使うことで、当初の要件は問題なくクリアできました。しかし、animated WebP の画像処理において、パフォーマンスが悪化することが分かりました。
具体的には、 10MB 程度の animated WebP を圧縮しようとすると、数十秒かかるということが分かりました。
圧縮方法を見直す
makeshop では、アップロードされた画像を 200KB 以下に圧縮する仕様になっています。
しかし govips には、サイズを指定して圧縮する機能がないため、スケールを徐々に下げながら 200KB 以下に圧縮することにしました。
ここで問題となったのが、animated WebP の圧縮にかかる時間です。10MB 程度の animated WebP では、1 回のループで 5 秒前後、ループ回数も比較的多いため、処理に数十秒かかってしまいます。
func compress(baseImg *vips.ImageRef, targetSize int) ([]byte, error) { compressed, err := baseImg.ToBytes() if err != nil { return nil, err } scale := 1.0 for range 10 { img, err := baseImg.Copy() if err != nil { return nil, err } if err := img.Resize(scale, vips.KernelLinear); err != nil { img.Close() return nil, err } compressed, _, err = img.ExportNative() img.Close() if err != nil { return nil, err } if targetSize > len(compressed) { break } scale -= 0.1 } return compressed, nil }
そこで、処理時間を短縮するために、ループ回数を減らす方法を検討しました。
スケールを徐々に下げて圧縮するのではなく、バイナリサーチにより最適なスケール値を効率的に見つけるようにしたところ、ループ回数をかなり少なくすることができました。
govips に与えるパラメータを見直す
govips には 圧縮した画像を出力する際のパラメータを変更することができます。
デフォルトのパラメータだと、animated WebP の出力に時間がかかるため、Quality(画質)や ReductionEffort(ファイルサイズを小さくするために費やすCPU負荷レベル)を変更することでチューニングを行いました。
func NewAnimatedWebpExportParams() *vips.WebpExportParams { params := vips.NewWebpExportParams() params.Quality = 50 // デフォルトは75 params.ReductionEffort = 1 // デフォルトは4 return params }
負荷試験で パフォーマンスの課題 を解決できたか確認する
k6 を使って、秒間1枚の animated WebP を処理できるか負荷試験を行いました。
開始数分で CPU 使用率・メモリ使用率がかなり高い値となり、Out Of Memory が発生しました。
よくよく調べると WebP の開発元である Google も言及しており、animated WebP のデコードには CPU とメモリを多く消費するようです。
アニメーション GIF と比較したアニメーション WebP のデメリット
妥協した点
これまで当初の要件を満たせるように、様々な試行錯誤を行ってきました。
しかし、animated WebP の圧縮には時間がかかり、パフォーマンス面でも課題があることが分かりました。
さらに、2MB 程度の animated WebP を圧縮してみたところ、画質の劣化が顕著であることも分かりました。
その結果、品質と要件のバランスを考慮し、animated WebP に対しては圧縮を行わないという判断に至りました。その代わり、当初の要件である「サイトの表示速度向上」を満たすため、animated WebP のアップロードは最大 1MB 以下に制限することにしました。
要件 | どう対応したか | |
---|---|---|
⭕️ | animated WebP に対応すること | govips を使ってアニメーションが失われないようにする。 |
❌️ | WebP を圧縮できること | animated WebP は圧縮することは課題があるため妥協。画像の品質を低下させずにサイトの表示速度を高めるため、animated WebP のアップロードは上限 1MB 以下とする。 |
では、animated WebP かどうかをどのように判定しているのか?
これには govips の機能を活用しています。govips には画像のメタデータを取得する機能があり、以下のようにして animated WebP かどうかを判定できます。
img, err := vips.LoadImageFromFile("input.webp", importParams) if err != nil { return err } if img.Metadata().Format == vips.ImageTypeWEBP && img.Pages() > 1 { // animated webp }
まとめ
このように、様々な試行錯誤を経て WebP をアップロードできるようになりました。
今回 animated WebP に関しては多くの調査を行いましたが、一般的によく使われているサイトでも対応していなかったり、アニメーションが失われてしまうケースも少なくないことがわかりました。
その点、makeshop ではアニメーションが失われることなくアップロードできます。 ぜひ WebP を活用して、より魅力的なショップページ作りにお役立ていただければと思います。