Rust で AVIF を生成してみる（Windows 編）

[Firefox も version 92.0 で AVIF をサポートする](https://www.mozilla.org/en-US/firefox/92.0beta/releasenotes/)ということで Windows 環境の Rust で AVIF を生成してみたいと思います。

## image crate
[image](https://crates.io/crates/image) crate が AVIF をサポートしているので使ってみます。↓こんな感じで書いてみました。JPEG -> AVIF 変換しています。

```toml:Cargo.toml
[package]
name = "avif_test"
version = "0.1.0"
authors = ["benki"]
edition = "2018"

[dependencies]
anyhow = "1.0"

[dependencies.image]
version = "0.23"
default-features = false
features = [
    "jpeg",
    "avif-encoder"
]
```

```rust:main.rs
use anyhow::Result;

fn main() -> Result<()> {
    let img = image::open(r"C:\Users\benki\Desktop\test.jpg")?;
    img.save_with_format(
        r"C:\Users\benki\Desktop\test.avif",
        image::ImageFormat::Avif,
    )?;
    Ok(())
}
```

`cargo run --release`すると…

```
error: failed to run custom build command for `rav1e v0.4.1`

Caused by:
  process didn't exit successfully: `C:\Users\benki\Documents\rust_projects\avif_test\target\release\build\rav1e-595ba7b94a49d3bb\build-script-build` (exit code: 101)
  --- stdout
  cargo:rustc-cfg=nasm_x86_64

  --- stderr
  thread 'main' panicked at 'This version of NASM is too old: 指定されたファイルが見つかりません。 (os error 2)', C:\Users\benki\.cargo\registry\src\github.com-1ecc6299db9ec823\rav1e-0.4.1\build.rs:251:7
  note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
warning: build failed, waiting for other jobs to finish...
error: build failed
```

エラーが出ました。[NASM](https://nasm.us/) が必要らしいのでインストールします。

## NASM インストール（不要）

インストール手順は [zip](https://www.nasm.us/pub/nasm/releasebuilds/2.15.05/win64/) をダウンロードして解凍してパスを通して再起動です。これでビルドできるでしょう。`cargo run --release`すると…

```
error: failed to run custom build command for `rav1e v0.4.1`

Caused by:
  process didn't exit successfully: `C:\Users\benki\Documents\rust_projects\avif_test\target\release\build\rav1e-595ba7b94a49d3bb\build-script-build` (exit code: 101)
  --- stdout
  cargo:rustc-cfg=nasm_x86_64
  running: "nasm" "-fwin64" "-IC:\\Users\\benki\\Documents\\rust_projects\\avif_test\\target\\release\\build\\rav1e-24085274cf597553\\out/" "-Isrc/" "C:\\Users\\benki\\.cargo\\registry\\src\\github.com-1ecc6299db9ec823\\rav1e-0.4.1\\src/x86/mc_sse.asm" "-o" "C:\\Users\\benki\\Documents\\rust_projects\\avif_test\\target\\release\\build\\rav1e-24085274cf597553\\out\\src/x86/mc_sse.o"

（中略）

  --- stderr
  Some((2, 14, 0)) "NASM version 2.15.05 compiled on Aug 28 2020\r\n" (2, 15, 5)
  C:\Users\benki\.cargo\registry\src\github.com-1ecc6299db9ec823\rav1e-0.4.1\src/x86/sse.asm:446: warning: dropping trailing empty parameter in call to multi-line macro `LOAD_SCALES_8X4' [-w+macro-params-legacy]

（中略）

  C:\Users\benki\.cargo\registry\src\github.com-1ecc6299db9ec823\rav1e-0.4.1\src/x86/tables.asm:534: warning: dropping trailing empty parameter in call to multi-line macro `const' [-w+macro-params-legacy]
  thread 'main' panicked at 'NASM build failed. Make sure you have nasm installed. https://nasm.us: "failed to spawn process: 指定されたフ 
ァイルが見つかりません。 (os error 2)"', C:\Users\benki\.cargo\registry\src\github.com-1ecc6299db9ec823\rav1e-0.4.1\build.rs:116:6
  note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```

だめだー！:sob:
パス文字列がダメなのかと思って、rav1e の build.rs を弄ってみましたがやっぱりビルドできないので、asm feature を使わないことにします。

## [patch]
image crate は AVIF エンコーダとして、[ravif](https://crates.io/crates/ravif) を使用しています。ravif は [rav1e](https://crates.io/crates/rav1e) に依存しています。ravif が rav1e の asm feature を使用しているので、これを無効にします。

sub dependencies の`features`をどうやって設定するのか悩んだのですが、Cargo.toml の`[patch]`を使って ravif の Cargo.toml を書き換えることにしました。

まず ravif-0.6.4 のソースコードを丸ごと src フォルダにコピー。src/ravif-0.6.4/Cargo.toml の 56~57 行目をコメントアウトします。

```toml:src/ravif-0.6.4/Cargo.toml
[features]
# asm = ["rav1e/asm"]
# default = ["asm"]
```

そして、avif_test の Cargo.toml に以下を追加。

```toml:Cargo.toml
[patch.crates-io]
ravif = { path = "src/ravif-0.6.4" }
```

これで crates.io リポジトリの ravif ではなく、ローカルのものが使用されます。便利。`cargo run --release`すると…

```
   Compiling avif_test v0.1.0 (C:\Users\benki\Documents\rust_projects\avif_test)
    Finished release [optimized] target(s) in 2.56s
     Running `target\release\avif_test.exe`
```

NASM なしでビルドできました。無事 test.avif も生成されました。やったぜ。
でも何やら様子がおかしい…

| test.jpg | test.avif |
|:-:|:-:|
| 204 KB | 566 KB |

元の JPEG よりファイルサイズ大きくなってるんですが？:thinking:
どうやら image crate の`save_with_format(path, image::ImageFormat::Avif)`は最高品質で AVIF を生成するようです。

## もっと圧縮

ということで、もっとファイルサイズを圧縮するコードを書きました。全部まとめてどうぞ。

```toml:Cargo.toml
[package]
name = "avif_test"
version = "0.1.0"
authors = ["benki"]
edition = "2018"

[dependencies]
anyhow = "1.0"

[dependencies.image]
version = "0.23"
default-features = false
features = [
    "jpeg",
    "avif-encoder"
]

[patch.crates-io]
ravif = { path = "src/ravif-0.6.4" }
```

```toml:src/ravif-0.6.4/Cargo.tomlの55~57行目
[features]
# asm = ["rav1e/asm"]
# default = ["asm"]
```

```rust:main.rs
use anyhow::{Context, Result};
use image::{avif::AvifEncoder, ColorType::Rgb8, GenericImageView};
use std::fs::File;
use std::io::BufWriter;

fn main() -> Result<()> {
    let img = image::open(r"C:\Users\benki\Desktop\test.JPG")?;
    let data = img.as_rgb8().context("no rgb8")?.to_vec();
    let fout = &mut BufWriter::new(File::create(r"C:\Users\benki\Desktop\test.avif")?);

    // Create a new encoder with specified speed and quality, that writes its output to `w`.
    // `speed` accepts a value in the range 0-10, where 0 is the slowest and 10 is the fastest.
    // `quality` accepts a value in the range 0-100, where 0 is the worst and 100 is the best.
    // pub fn new_with_speed_quality(w: W, speed: u8, quality: u8) -> Self
    AvifEncoder::new_with_speed_quality(fout, 5, 60).write_image(
        &data,
        img.width(),
        img.height(),
        Rgb8,
    )?;
    Ok(())
}
```

`cargo run --release`すると…

| test.jpg | test.avif |
|:-:|:-:|
| 204 KB | 126 KB |

けっこう小さくなりました。`new_with_speed_quality`の`speed`と`quality`の値を変えて画質とファイルサイズのバランスを調整すると良いでしょう。`speed`を小さくすると画質はよくなりますが、画像の生成に現実的でないくらいの時間が掛かります。何事もバランスが大事です。

さて肝心の Firefox でのサポート状況なんですが、生成した test.avif を Firefox で開いても「データが壊れている」とエラーが出て表示できませんでした…:sob:。VSCode だとちゃんと表示されるんですけどねぇ。

[Netflix の AVIF サンプル](http://download.opencontent.netflix.com/?prefix=AV1/Chimera/AVIF/)も Firefox ではエラーで表示できないので、Firefox のデコーダがおかしいのかもしれません…。おわり。