アニメーションWebP(Animated WebP)の作り方

# はじめに
複数のWebPファイルを元にアニメーションWebP(Animated WebP)を作成するプログラムを実装しました。

# WebPについて

* 米Googleが開発しているオープンな静止画像フォーマットです。
* ウェブサイトのトラフィック量軽減と表示速度短縮を目的に、JPEGやGIF、PNGの置き換えを意図する規格だそうです。
* 各種ブラウザも表示対応しています。（`Chrome`、`Firefox`、`Edge`、`Safari`、`Opera`）
* アニメーション形式にも対応しています。

[Wikipedia](https://ja.wikipedia.org/wiki/WebP)より

# 環境
WebPファイルのアニメーションを作成するために、[libwebp](https://developers.google.com/speed/webp/download)を使用します。
WebPの静止画像を作成するだけなら、[こちら](https://qiita.com/devmynote/items/99ece25c31965f6243d2)で紹介した方法もあります。

* Windows 10
* dmd v2.098.0
* libwebp ver 1.2.2 windows x64版

# ソースコード
以下に、実装したソースコードを紹介します。

```d:awebp.d
/+ dub.sdl:
	name "awebp"
	dependency "libwebp-d" repository="git+https://github.com/devmynote/libwebp-d.git" version="3c927671d7fef977a793cf3da0850104dc865216"
	lflags "/LIBPATH:./lib"
+/
// dub build --build=release --arch=x86_64 --single awebp.d
import std.stdio;
import std.string;
import core.stdc.stdio;
import webp.decode;
import webp.encode;
import webp.mux;
import webp.muxtypes;
import webp.types;

ubyte[] loadWebP(string ifile)
{
	auto file = File(ifile, "rb");
	scope(exit) file.close();
	auto size = file.size();
	ubyte[] buf = file.rawRead(new ubyte[size]);
	return ( buf );
}

void saveWebP(string ifile, string ofile)
{
	ubyte[] buf = loadWebP(ifile);
	auto file = File(ofile, "wb");
	scope(exit) file.close();
	file.rawWrite(buf);
}

ubyte* getImageRGBA(string ifile, int* width, int* height)
{
	ubyte[] buf = loadWebP(ifile);
	return ( WebPDecodeRGBA(buf.ptr, buf.length, width, height) );
}

void saveAnimatedWebP(string[] ifiles, string ofile, uint loopCount, ushort delayTime)
{
	WebPAnimEncoderOptions enc_options;
	WebPAnimEncoderOptionsInit(&enc_options);
	WebPConfig config = void;
	WebPConfigInit(&config);
	
	WebPAnimEncoder* enc;
	foreach ( i, ifile; ifiles ){
		int width, height;
		ubyte* rgba = getImageRGBA(ifiles[i], &width, &height);
		if ( i == 0 ){
			enc = WebPAnimEncoderNew(width, height, &enc_options);
		}
		WebPPicture frame;
		WebPPictureInit(&frame);
		frame.use_argb = 1;
		frame.width    = width;
		frame.height   = height;
		int stride = width * 4;
		WebPPictureImportRGBA(&frame, rgba, stride);
		WebPAnimEncoderAdd(enc, &frame, cast(int)i * delayTime, &config);
		WebPFree(rgba);
	}
	WebPAnimEncoderAdd(enc, null, cast(int)ifiles.length * delayTime, null);
	WebPData webp_data;
	WebPAnimEncoderAssemble(enc, &webp_data);
	scope(exit) WebPFree(cast(void*)webp_data.bytes);
	WebPMux*	 mux = WebPMuxCreate(&webp_data, 1);
	WebPMuxAnimParams anim_params;
	anim_params.loop_count = loopCount;
	WebPMuxSetAnimationParams(mux, &anim_params);
	WebPMuxAssemble(mux, &webp_data);
	
	FILE* fp = fopen(ofile.toStringz, "wb"); 
	scope(exit) fclose(fp);
	fwrite(webp_data.bytes, webp_data.size, ubyte.sizeof, fp);
	
	WebPMuxDelete(mux);
	WebPAnimEncoderDelete(enc);
}

void main(string[] args)
{
	if ( args.length == 3 ){
		saveWebP(args[1], args[2]);
	} else {
		uint   loopCount = 0;	// 0:infinite
		ushort delayTime = 500;	// delayTime * ms
		saveAnimatedWebP(args[1 .. $-1], args[$-1], loopCount, delayTime);
	}
}
```

# ソースコード補足
## loadWebP
入力WebPファイル`ifile`をメモリに読み込みます。

## saveWebP
入力WebPファイルが１つの場合、アニメーションWebPファイルを作成せず静止画像WebPファイル`ofile`を作成します。

## getImageRGBA
入力WebPファイルから画像データ(RGBA)を抽出します。

## saveAnimatedWebP
アニメーションWebPファイル作成の本処理となります。

* 複数の入力WebPファイルから画像データを抽出して、出力用アニメーションWebPファイルにセットします。
* アニメーションWebPファイルの幅、高さは1つ目の入力WebPファイルの情報を使います。このため、すべての入力WebPファイルはフレームの幅、高さが同じであることが前提です。
* `loopCount`は、アニメーションの繰り返し回数です。`0`をセットすると無制限に繰り返します。
* `delayTime`は、各画像の表示間隔です。単位はミリ秒です。

# コンパイル

##  libwebpの入手
[A new image format for the Web](https://developers.google.com/speed/webp/)より、[libwebp-1.2.2-windows-x64.zip](https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-1.2.2-windows-x64.zip)をダウンロードします。
`libwebp-1.2.2-windows-x64.zip`内の`lib`を以下に配置します。

```text:コマンドプロンプト
D:\Dev\AWebP> tree
フォルダー パスの一覧:  ボリューム ****
ボリューム シリアル番号は ****-**** です
D:.
└─lib

D:\Dev\AWebP> dir lib
フォルダー パスの一覧:  ボリューム ****
ボリューム シリアル番号は ****-**** です

 D:\Dev\AWebP\lib のディレクトリ

2021/03/20  18:23    <DIR>          .
2021/03/20  18:23    <DIR>          ..
2021/01/21  12:53         1,658,226 libwebp.lib
2021/01/21  12:53            42,868 libwebpdemux.lib
2021/01/21  12:53           118,690 libwebpmux.lib
```

`lib`が配置できたら、コンパイルを実行するだけです。

```text:コマンドプロンプト
D:\Dev\AWebP> dub build --build=release --arch=x86_64 --single awebp.d
parsePackageRecipe dub.sdl
Performing "release" build using D:\App\Dev\dmd2\windows\bin64\dmd.exe for x86_64.
awebp ~master: building configuration "application"...
Linking...
```

# 実行、実行結果
コマンド実行例です。実行結果は[こちら](https://devmynote.github.io/ImageSample/awebp1.html)

```text:コマンドプロンプト
D:\Dev\AWebP> awebp i1.webp i2.webp i3.webp i4.webp i5.webp ani.webp
```

# おまけ１：静止画像ファイルの作成
副産物として、適当なWebP静止画像ファイルの作成例を紹介します。

```d:iwebp.d
/+ dub.sdl:
	name "iwebp"
	dependency "libwebp-d" repository="git+https://github.com/devmynote/libwebp-d.git" version="3c927671d7fef977a793cf3da0850104dc865216"
	lflags "/LIBPATH:./lib"
+/
// dub build --build=release --arch=x86_64 --single iwebp.d
import std.string;
import core.stdc.stdio;
import webp.encode;
import webp.types;

void saveImageWebP(string ofile, ubyte[] rgba, int width, int height)
{
	int stride = width * 4;
	ubyte* output;
	size_t size = WebPEncodeLosslessRGBA(rgba.ptr, width, height, stride, &output);
	scope(exit) WebPFree(output);
	FILE* fp = fopen(ofile.toStringz, "wb"); 
	scope(exit) fclose(fp);
	fwrite(output, size, ubyte.sizeof, fp);
}

void saveImageSample()
{
	int width  = 200;
	int height = 100;
	ubyte[] rgba = new ubyte[width * height * 4];
	for ( int y = 0; y < height; y++ ){
		for ( int x = 0; x < width; x++ ){
			int ind = x * 4 + y * width * 4;
			rgba[ind    ] = 0;
			rgba[ind + 1] = 64;
			rgba[ind + 2] = 128;
			rgba[ind + 3] = 255;
		}
	}
	saveImageWebP("image.webp", rgba, width, height);
}

void main(string[] args)
{
	saveImageSample();
}
```

# おまけ２：dstepを使ってみた
今回、`libwebp`のライブラリを使用するためのバインドファイル（パッケージ）を作成しました。
おまけとして、バインドファイルを作成した際のメモ書きをまとめました。
参考：[C言語のヘッダーファイルをD言語用に変換する dstep の使い方](https://qiita.com/lempiji/items/122c49a6c67a5b6d37a0)

## dstep使用時の環境

* Windows 10
* dmd v2.096.0
* clang version 9.0.0
* Visual C++ ビルドツール 2019
* libwebp-1.2.2-windows-x64.zip
* dstep version 1.0.2

## dstepのビルド
私の開発環境の場合、clangのインストールフォルダが標準と異なるため、ライブラリ`libclang.lib`が見つからないというエラーが発生しました。

```text:コマンドプロンプト
LINK : fatal error LNK1181: 入力ファイル 'libclang.lib' を開けません。
Error: linker exited with status 1181
D:\Dev\dmd2\windows\bin\dmd.exe failed with exit code 1.
```

dstepの`dub.json`の`/LIBPATH`を`libclang.lib`があるフォルダに修正します。（修正箇所は２か所あります）
以下は、`D:\Dev\LLVM\lib`フォルダを指定する場合の修正例です。

```json:dub.json
"lflags-windows-x86_64": [
    "/LIBPATH:D:\\Dev\\LLVM\\lib",
    "libclang.lib",
    "Ole32.lib"
],
```

## dstepの実行
`libwebp-1.2.2-windows-x64.zip`の`include`を`D:\Dev\AWebP`に配置しました。
その後で、`dub`コマンドを順番に実行しました。出力先フォルダとして`source`を作成しています。
コマンド実行中に`Deprecated`のメッセージが出ますが、特に対処不要です。問題なくファイルが変換されました。
大半の変換はうまくいったので、とても便利だと感じました。ただし、すべて自動変換というわけにはいかず、一部手を加えました。
完成したファイルは[こちら](https://github.com/devmynote/libwebp)にもあります。

```text:コマンドプロンプト
D:\Dev\AWebP> dub run dstep -- include\webp\decode.h    -o source/decode.d
D:\Dev\AWebP> dub run dstep -- include\webp\demux.h     -o source/demux.d
D:\Dev\AWebP> dub run dstep -- include\webp\encode.h    -o source/encode.d
D:\Dev\AWebP> dub run dstep -- include\webp\mux.h       -o source/mux.d
D:\Dev\AWebP> dub run dstep -- include\webp\mux_types.h -o source/muxtypes.d
D:\Dev\AWebP> dub run dstep -- include\webp\types.h     -o source/types.d
```

# 参考情報
[A new image format for the Web](https://developers.google.com/speed/webp/)
[libwebp-1.2.2-windows-x64.zip](https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-1.2.2-windows-x64.zip)
[WebPAnimEncoder API](https://developers.google.com/speed/webp/docs/container-api#webpanimencoder_api)
[C++ (Cpp) WebPAnimEncoderAssemble Examples](https://cpp.hotexamples.com/examples/-/-/WebPAnimEncoderAssemble/cpp-webpanimencoderassemble-function-examples.html)
[C言語のヘッダーファイルをD言語用に変換する dstep の使い方](https://qiita.com/lempiji/items/122c49a6c67a5b6d37a0)
[dub v1.23.0/Support dependencies as git url with exact commit](https://qiita.com/simd_nyan/items/428372ef698a586cd01d)
[アニメーションWebPの作り方](https://qiita.com/Yatoichi/items/4335269223c0e3dc9921)

# 更新履歴
2021.3.20 初回投稿
2022.1.26 バージョン1.2.2にあわせて更新