gulpでjpg・pngからAVIF画像を生成する

gulpでjpg・pngからAVIF画像を生成する

はじめに

以前、gulpでjpg・pngからWebP画像を生成する方法について書きました。

WebPへの変換は画像軽量化にかなり効果がありましたが、WebPよりさらに圧縮効率が良く高画質な「AVIF」という画像形式があります。この記事の執筆時(2022.09.14)にはSafari16が登場し、AVIF形式の画像がモダンブラウザ(Edge以外)で表示可能になりつつあります。

そろそろ対応方法を考える時期に来たかなというところです。AVIFもWebPのようにgulpでサクッと生成できる方法があるので今回はその方法についてメモしておきます。

AVIF画像とは

「AVIF」は「AV1 Image File Format」の略称で、動画圧縮方式の「AV1」と共通の手法で画像を圧縮し、HEIF形式のファイルとして保存する次世代型のファイルフォーマットのことです。拡張子は.avif

高画質・WebPよりも高い圧縮率・透過もOKです。

▼参考URL

https://www.sedesign.co.jp/blog/wat-is-avif

https://e-words.jp/w/AVIF.html

https://ipeinc.jp/blog/avif/

いいですね。早速jpg・pngから変換してみましょう。

gulpでjpg・pngをAVIFに変換

まずは必要なパッケージをインストールします。

$ yarn add gulp gulp-libsquoosh gulp-rename

gulp-libsquoosh

Googleが開発した画像圧縮ツール「Squoosh(スクーシュ)」というものをgulpから扱えるパッケージです。

Squooshは画像の圧縮のほかに、WebPやAVIFといった画像フォーマットへの変換などができます。それらの機能をNode.jsで扱えるようにした「libSquoosh」が提供されており、それがベースとなったパッケージです。

WebP画像もAVIF画像も両方生成でき、さらに画像圧縮まで可能でとても便利。

詳しい使い方は下記をご覧ください。

https://github.com/pekeq/gulp-libsquoosh#readme

gulp-rename

こちらはgulpで出力するファイルの名前を変更できるパッケージ。

過去記事でも使っています。今回も出力するファイル名を調整したいので使います。

詳しい使い方は下記。

https://github.com/hparra/gulp-rename#readme

gulpタスクを書く

gulpfile.jsに画像変換処理のタスクを書いていきます。

const gulp = require('gulp');
const squoosh = require('gulp-libsquoosh');
const rename = require('gulp-rename');

function imageConversion() {
  return gulp.src('assets/images/**/*.+(jpg|jpeg|png)')
  .pipe(rename((path) => {
    path.basename += path.extname;
  }))
  .pipe(squoosh({
    encodeOptions: {
      webp: {
        quality: 90,
      },
      avif: {
        quality: 90,
      }
    }
  }))
  .pipe(gulp.dest('assets/images'));
}
exports.imageConversion = imageConversion;

画質を90%に圧縮して生成するようにします。

あとはyarn gulp imageConversionで実行するとjpg・pngからwebpとavifが生成できます。

生成された画像のサイズは下記のような感じ↓

  • jpeg 215KB → avif 45KB、webp 178KB
  • png 638KB → avif 19KB、webp 81KB

あくまでも一例ですし、画像にもよりますが、かなりの容量節約になりそうです。特にpng。

ファイルのリネームの必要性について

リネーム処理(ファイル名の変更)を通さずに画像を生成すると「sample.jpg」という画像は「sample.webp」「sample.svif」という画像に変わります。

一見すると問題無さそうに見えますね。

ですが、「sample.png」という別の拡張子が付いたファイルも同じディレクトリにあったとすると、それらすべてが「sample.avif」と「sample.webp」を生成してしまいます。これだとどちらの画像も生成後に同じ名前になってしまいますので、どちらかが上書きされて消えてしまいます。(ファイルの命名規則など管理体制でどうにかなるとかはまた別の話)

こうした理由からファイル名を重複させずに生成する回避策としてリネーム処理を通すことが必要になります。

方法は色々と考えられますが、ここでは生成ファイル画像にあえて拡張子を残した状態でファイル名を付けるようにします。

  • sample.jpg → sample.jpg.webp、sample.jpg.avif
  • sample.png → sample.png.webp、sample.png.avif

こうすれば、重複せずに画像を生成できます。

その処理が下記のコードです。

.pipe(rename((path) => {
  //path.basename → sample.jpg の 「sample」 
  //path.extname → sample.jpg の 「.jpg」
  path.basename += path.extname; //拡張子より前の名前の部分に拡張子の名前も追加してしまう
}))

.htaccessにWebPとAVIF対応の記述を追記

単に生成しただけだと、<picture>タグで下記のように表示する画像を手動で切り替えなくてはいけないので面倒です。

<picture>
  <source type="image/avif" srcset="sample.avif">
  <source type="image/webp" srcset="sample.webp">
  <img src="sample.jpg" alt ="">
</picture>

ここにさらにレスポンシブイメージが加わると、ブレイクポイントに合わせて別の画像を出せる様に書き加えていかないといけないのでさらに記述が増えて面倒くさくなりますし、コードの見通しが悪くなるので後から修正するのも大変です。

そのため、サーバー側でavifやwebpを配信できるかできないかを判定して出し分ける方が簡単です。

具体的には.htaccessに下記を追記します。

<IfModule mod_rewrite.c>
RewriteEngine On

# avif
RewriteCond %{HTTP_ACCEPT} image/avif
RewriteCond %{REQUEST_FILENAME} (.*)\.(jpe?g|png|gif)$
RewriteCond %{REQUEST_FILENAME}\.avif -f
RewriteCond %{QUERY_STRING} !type=original
RewriteRule (.+)\.(jpe?g|png|gif)$ %{REQUEST_URI}.avif [T=image/avif,L]

# webp
RewriteCond %{HTTP_ACCEPT} image/webp
RewriteCond %{REQUEST_FILENAME} (.*)\.(jpe?g|png|gif)$
RewriteCond %{REQUEST_FILENAME}\.webp -f
RewriteCond %{QUERY_STRING} !type=original
RewriteRule (.+)\.(jpe?g|png|gif)$ %{REQUEST_URI}.webp [T=image/webp,L]
</IfModule>

<IfModule mod_headers.c>
<FilesMatch "\.(jpe?g|png|gif)$">
Header append Vary Accept
</FilesMatch>
</IfModule>

<IfModule mod_mime.c>
AddType image/webp  .webp
AddType image/avif  .avif
</IfModule>

内容的にはjpegやpngファイルを開く時にブラウザがavif対応しており、かつ拡張子.avifファイルが存在している場合はavifを配信、avifファイルがない場合は、webp対応かつ拡張子.webpファイルが存在している場合はwebpを配信、という処理になっています。

もともとWordPressでWebP対応した際に「EWWW Image Optimizer」という画像圧縮プラグインの設定で下記の記述を.htaccessへ追記していました。

<IfModule mod_rewrite.c>
	RewriteEngine On
	RewriteCond %{HTTP_ACCEPT} image/webp
	RewriteCond %{REQUEST_FILENAME} (.*)\.(jpe?g|png|gif)$
	RewriteCond %{REQUEST_FILENAME}\.webp -f
	RewriteCond %{QUERY_STRING} !type=original
	RewriteRule (.+)\.(jpe?g|png|gif)$ %{REQUEST_URI}.webp [T=image/webp,L]
</IfModule>
<IfModule mod_headers.c>
	<FilesMatch "\.(jpe?g|png|gif)$">
		Header append Vary Accept
	</FilesMatch>
</IfModule>
AddType image/webp .webp

それをAVIF対応するために書き換えたものになります。内容は下記記事を参考にしました。

https://qiita.com/nanakochi123456/items/adedfe54e50bbb3653d5

これでAVIFがあればAVIFを、なければWebPを、それがなければjpg・pngを配信するようにサーバーの振る舞いを変えることができました。

WordPressで使っていた記述ではありますが、.htaccessが使えるサーバーであれば問題なく使えます。

ただし.htaccessは記述をミスると画面真っ白になったり500エラーを吐いたりする場合があります。追記する場合は、すぐに元に戻せるように必ずバックアップファイルを準備しておきましょう。

WordPressのメディアにAVIFをアップロード可能にする

デフォルトのままだとメディアにAVIF形式の画像をアップロードするとエラーが出てしまいます。

AVIFをアップロード許可するためにfunctions.phpに下記を追記します。

add_filter('upload_mimes', function($mimes) {
  $mimes['avif'] = 'image/avif';
  return $mimes;
});

add_filter('ext2type', function($types) {
  $types['image'][] = 'avif';
  return $types;
});

add_filter('wp_check_filetype_and_ext', function($data, $file, $filename, $mimes) {
  $filetype = wp_check_filetype( $filename, $mimes );
  return ['ext'=>$filetype['ext'], 'type'=>$filetype['type'], 'proper_filename'=>$data['proper_filename']];
}, 10, 4 );

これでアップロードできるようになりました。

下記記事を参考にしました。

https://varypre.com/avif-wordpress/

WordPress上でAVIF変換できる無料プラグインはある?

WordPressでは一括でAVIFに変換できるプラグインは無料ではまだない様です。(2022.09.15)

EWWW Image Optimizerは有料プランならあるみたいです。

そのためローカルで画像を用意してgulpで一括変換してから記事にアップロードする方法が今のところまだマシな方法かなと思います。自作でプラグイン作るのもちょっとハードル高めな気がするので。

他はオンラインでSquooshを使って変換する方法もありますが、こちらは1枚ずつなので効率悪め。

ちょっと検索してみても意外とまともなツールが出てこないので、今のところ複数同時生成したい場合はローカル環境で変換するのが確実で、その中でもgulpが1番手軽に導入して使えると思います。

ちょっと手間でもwebpackやViteを使いたい場合は、ICS MEDIAさんの下記の記事を参考にすれば間違いないと思います。

https://ics.media/entry/220204/

おわりに

今回はAVIF対応について書きました。

WebPより軽くてけっこう使えそう。画像によりけりではありますが、変換してみて容量が大体WebPの3分の1ぐらいかそれ以下になりました。

ただEdgeで使えないのはなんかイラッとしますね。IEの次はお前かいー!っていう。笑

今日はこのへんで。