AVIFファイルの作成

# はじめに
AVIF形式の画像ファイルについて作成処理を実装しました。
その際のまとめ記事になります。

# AVIFとは
[AVIF](https://ja.wikipedia.org/wiki/AV1#AV1_Image_File_Format%EF%BC%88AVIF%EF%BC%89)とは、AV1 Image File Formatのことです。AV1というコーデックを使って圧縮を行います。
他の画像形式ファイルと比べて、ファイル圧縮に多少時間がかかりますが、ファイルサイズを大幅に削減できます。

ブラウザでは、`Chrome`、`Firefox`、`Opera`が対応しています。
ただし、`Firefox`の場合、設定変更（`about:config`）を行う必要があります。
→`image.avif.enabled`を`true`

# 環境
AVIFファイルを作成するために、[libavif](https://github.com/AOMediaCodec/libavif)を使用します。

* Windows 10
* dmd v2.096.0
* libavif v0.9.0
* libaom v2.0.2
* libdav1d v0.8.2

## libavifの入手
libavifの[Releases](https://github.com/AOMediaCodec/libavif/releases)から入手可能です。
この記事で使用したバージョンは、[libavif_vs2019_x64_4fedf0bb_Release.zip](https://github.com/AOMediaCodec/libavif/releases/download/v0.9.0/libavif_vs2019_x64_4fedf0bb_Release.zip)です。

## libaom、libdav1dの入手
以下の説明の通り、コンパイルして作成する必要があります。

今回私が`Windows10`向けにコンパイルしたライブラリについては、GitHubに公開しています。
[aom_v2.0.2.zip](https://devmynote.github.io/ImageSample/lib/aom_v2.0.2.zip)
[libdav1d_v0.8.2.zip](https://devmynote.github.io/ImageSample/lib/libdav1d_v0.8.2.zip)

## libaom、libdav1dの作成
`libaom`、`libdav1d`をコンパイルして作成する場合、以下の事前インストールが必要です。
参考までに、私の環境でのバージョンをあわせて記載します。

* Active Perl (v5.28.1 built for MSWin32-x64-multi-thread. Built Oct 28 2020 18:24:51)
* CMake (version 3.15.19101501-MSVC_2)
* Git (version 2.24.1.windows.2)
* meson (0.57.1)
* nasm (version 2.15.05 compiled on Aug 28 2020)
* ninja (1.8.2)
* Visual Stdio (2019)

インストール後に[libavif v0.9.0に対応しているこちら](https://github.com/AOMediaCodec/libavif/tree/v0.9.0/ext)から、`aom.cmd`と`dav1d.cmd`を取得します。
`aom.cmd`を実行することで、`aom\build.libavif`フォルダに`aom.lib`が作成されます。
`dav1d.cmd`を実行することで、`dav1d\build\src`フォルダに`libdav1d.a`が作成されます。これを`libdav1d.lib`にリネームして使用しています。

# ソースコード
JPEGやPNGなどの画像ファイルからAVIFファイルを作成する処理の実装例です。
`lib`フォルダを作成し、`avif_x64_Release.lib`、`aom.lib`、`libdav1d.lib`を格納します。

```d:convAvif.d
/+ dub.sdl:
	name "convAvif"
	dependency "bindbc-freeimage" version=">=0.3.0"
	dependency "libavif-d" version="~>0.9.0"
	versions "FI_318"
	lflags "/NODEFAULTLIB:libcmt.lib"
+/
// dub build --single --build=release convAvif.d
import std.conv;
import std.stdio;
import std.string;
import std.windows.charset;

import avif.avif;
import bindbc.freeimage;

pragma(lib, "lib/avif_x64_Release.lib");
pragma(lib, "lib/aom.lib");
pragma(lib, "lib/libdav1d.lib");

void main(string[] args)
{
	// Argument check
	if ( args.length < 3 ){
	    writeln("Argument required : input output");
		return;
	}
	string ifile = args[1].toMBSz.to!string;
	string ofile = args[2].toMBSz.to!string;
	// Load FreeImage
	FISupport fis = loadFreeImage();
	if ( fis != fiSupport ){
		if ( fis == FISupport.noLibrary ){
			writeln("FISupport.noLibrary");
		} else if ( FISupport.badLibrary ){
			writeln("FISupport.badLibrary");
		}
		return;
	}
	// Load Image
	FREE_IMAGE_FORMAT fif = FreeImage_GetFileType(ifile.toStringz, 0);
	FIBITMAP *dib = FreeImage_Load(fif, ifile.toStringz, 0);
	scope(exit) FreeImage_Unload(dib);
	if ( dib == null ){
		writeln("FreeImage_Load error");
		return;
	}
	if ( FreeImage_GetBPP(dib) != 32 ){
		FIBITMAP *dibold = dib;
		dib = FreeImage_ConvertTo32Bits(dibold);
		FreeImage_Unload(dibold);
	}
	FIMEMORY* hmem = FreeImage_OpenMemory();
	FreeImage_SaveToMemory(FIF_BMP, dib, hmem, 0);
	scope(exit) FreeImage_CloseMemory(hmem);
	ubyte* mem;
	uint bufsize;
	FreeImage_AcquireMemory(hmem, &mem, &bufsize);
	ulong offset = (*(mem+13)<<24) + (*(mem+12)<<16) + (*(mem+11)<<8) + *(mem+10);
	mem += offset;
	
	int orgw = FreeImage_GetWidth(dib);
	int orgh = FreeImage_GetHeight(dib);
	
	avifEncoder* encoder = avifEncoderCreate();
	encoder.codecChoice = avifCodecChoice.AVIF_CODEC_CHOICE_AOM;
	encoder.minQuantizer = 0;
	encoder.maxQuantizer = 31;
	encoder.minQuantizerAlpha = 0;
	encoder.maxQuantizerAlpha = 31;
	encoder.speed = 5;
	scope(exit) avifEncoderDestroy(encoder);
	
	avifImage* image = avifImageCreate(orgw, orgh, 8, avifPixelFormat.AVIF_PIXEL_FORMAT_YUV444);
	scope(exit) avifImageDestroy(image);
	avifRGBImage rgb;
	avifRGBImageSetDefaults(&rgb, image);
	scope(exit) avifRGBImageFreePixels(&rgb);
	avifRGBImageAllocatePixels(&rgb);
	
	for ( int y = 0; y < orgh; y++ ){
		for ( int x = 0; x < orgw; x++ ){
			int ind1 = x * 4 + y * orgw * 4;
			int ind2 = x * 4 + (orgh - 1 - y) * orgw * 4;
			*(rgb.pixels + ind1    ) = *(mem + ind2 + 2);	// R
			*(rgb.pixels + ind1 + 1) = *(mem + ind2 + 1);	// G
			*(rgb.pixels + ind1 + 2) = *(mem + ind2    );	// B
			*(rgb.pixels + ind1 + 3) = *(mem + ind2 + 3);	// A
		}
	}
	avifImageRGBToYUV(image, &rgb);
	avifEncoderAddImage(encoder, image, 0, avifAddImageFlags.AVIF_ADD_IMAGE_FLAG_SINGLE);
	
	avifRWData avifOutput;
	avifEncoderFinish(encoder, &avifOutput);
	scope(exit) avifRWDataFree(&avifOutput);
	auto f = fopen(ofile.toStringz, "wb");
	scope(exit) fclose(f);
	fwrite(avifOutput.data, 1, avifOutput.size, f);
}
```

# ソースコード補足

* `FreeImage`を使って、変換元ファイルを読み込みます。
* メモリ`mem`上には、ピクセル情報が左下から右方向に格納されています。各ピクセルは`BGRA`の`32`ビットで構成されます。
* `avifEncoder`は、AVIFファイルを作成（エンコード）するときのパラメータを設定できます。
* `avifRGBImage`には、ピクセル情報を左上から右方向に格納していきます。各ピクセルは`RGBA`の`32`ビットで構成されます。
`mem`とは格納順が異なる点に注意してください。

# コンパイル、実行例

```text:コマンドプロンプト
D:\Dev\Avif> dub build --single --build=release convAvif.d
Fetching libavif-d 0.9.0 (getting selected version)...
Performing "release" build using D:\Dev\dmd2\windows\bin\dmd.exe for x86_64.
bindbc-loader 0.3.2: target for configuration "noBC" is up to date.
bindbc-freeimage 0.5.0: target for configuration "dynamic" is up to date.
convAvif ~master: building configuration "application"...
Linking...
To force a rebuild of up-to-date targets, run again with --force.
```

コンパイルしたモジュール`convAvif.exe`を実行するには、`FreeImage.dll`が必要です。
[ダウンロードページ](https://freeimage.sourceforge.io/download.html)より、入手してください。

# エンコードが遅い場合の対処方法
変換元画像が大きい場合、ファイル作成（エンコード）にかなりの時間がかかる可能性があります。
その場合、`convAvif.d`にある以下のパラメータを調整してください。
`maxQuantizer`と`maxQuantizerAlpha `は、`0`から`63`を設定することができます。

* `0`をセットすると、処理速度は高速、画質は最悪、ファイルサイズは小さめ
* `63`をセットすると、画質は最高、処理速度は遅い、ファイルサイズは大きめ

```d:
	encoder.maxQuantizer = 31;
	encoder.maxQuantizerAlpha = 31;
```

# 参考
[AVIF：新しい次世代の画像圧縮形式の使い方](https://lpeg.info/webworks/image_avif.html)
[AV1 Image File Format](https://aomediacodec.github.io/av1-avif/)
[AVIF(wikipedia)](https://ja.wikipedia.org/wiki/AV1#AV1_Image_File_Format%EF%BC%88AVIF%EF%BC%89)
[AVIF(AV1 Image File Format)についてのメモ](https://qiita.com/tetsu_koba/items/06728837ad0103f202e8)