C++11 ヘッダーオンリーで APNG をデコードする

[**APNG**](https://ja.wikipedia.org/wiki/Animated_Portable_Network_Graphics)  をC++11ヘッダーオンリーでデコードしてみたというお話。


![apng](https://upload.wikimedia.org/wikipedia/commons/1/14/Animated_PNG_example_bouncing_beach_ball.png)


↑ 対応ブラウザなら動いて見えます。

仕様自体は結構前からあるらしいが、最近 LINE の動くスタンプだの、Chromium で表示できるようになっただので、にわかに注目されている気がする。

このデコーダを C++11 で実装して github で公開してみた。

* [uc::apng::loader](https://github.com/uctakeoff/uc_apng_loader)


この `uc_apng_loader.h` 1つで・・・と言いたいところだが、別途 [stb_image.h](https://github.com/nothings/stb) が必要だ。
しかし、ヘッダーオンリーには違いなかろう。

## サンプルコード

デコード結果を `stb_image_write.h` を使って PNGファイルとして連番保存するコードは以下のように書ける。

```cpp:apng2pngs.cpp
#include "uc_apng_loader.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>

int main(int argc, char** argv)
{
	if (argc < 2) {
		std::cerr << "Usage : " << argv[0] << " [APNG filename]" << std::endl;
		return 1;
	}
	try {

		auto loader = uc::apng::create_file_loader(argv[1]);

		while (loader.has_frame()) {

            auto frame = loader.next_frame();

			std::ostringstream filename;
			filename << "out" << std::setw(3) << std::setfill('0') << frame.index << ".png";

			stbi_write_png(filename.str().c_str(), frame.image.width(), frame.image.height(), 4, frame.image.data(), frame.image.width() * 4);
		}

	} catch (std::exception& ex) {
		std::cout << "failed : " << ex.what() << std::endl;
	} 
	return 0;
}
```

ヘッダーオンリーなのでライブラリのリンクは要らない。

```bash
$ g++ -std=c++11 apng2pngs.cpp
```

これだけでOK.

主な登場人物は以下の3人。

* `loader`
* `frame`
* `image`


## `uc::apng::loader`

`loader` は以下のように作成する。

```cpp
// ファイルから読み込む
auto loader = uc::apng::create_file_loader("filename.apng");

// メモリから読み込む
// std::string stringdata;
auto loader = uc::apng::create_memory_loader(stringData);

// const char* buf, size_t buflen
auto loader = uc::apng::create_memory_loader(buf, buflen);
```

各フレームは、 `loader::has_frame()` が `true` の間、`loader::next_frame()` で取得できる。

以下のようにまとめて取得しても良い。

```cpp
std::vector<uc::apng::frame> frames;

frames.reserve(loader.num_frames());

while (loader.has_frame()) {
	frames.push_back(loader.next_frame());
}
```
`loader::num_frames()` で総フレーム数が、`loader::num_plays()` でループ回数が取得できる。

`loader::num_plays() == 0` なら無限ループである。


## `uc::apng::frame`

1フレームの情報は `uc::apng::frame` に格納される。

```cpp
struct frame
{
	size_t index;		//!< フレームのインデックス番号(0〜)
	image_t image;		//!< raw 32bit RGBA 画像 (後述)
	uint16_t delay_num;	//!< フレーム表示時間の分子
	uint16_t delay_den;	//!< フレーム表示時間の分母
	bool is_default;	//!< デフォルトイメージならtrue
};
```

このフレームは、`delay_num/delay_den` 秒間表示することを示している[^1]。
[^1]: これがフレームごとに指定されているということは、仕様上はフレームごとに表示時間が変わっても良いということになる。私は見たことないが。
</br>
APNG は PNG と互換性がある。
非対応のデコーダが普通に PNG として読み込めるように作られている。
その場合に表示されるフレームが *デフォルトイメージ* だ[^2]。

[^2]: だから、デフォルトイメージは1ファイルに1つだけのはずである。


## `uc::apng::image_t`

`uc::apng::image_t` は、デコード済み32ビットRGBA画像を格納する。

画像サイズは `image_t::width()` , `image_t::height()` で、RAWデータは `image_t::data()` で取得できる。

例えば OpenGL でテクスチャに渡す場合はこうだろう。

```cpp
// const uc::apng::image_t image;
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 
		0, GL_RGBA, GL_UNSIGNED_BYTE, image.data());
```

## 参考

APNG仕様についてはここを参考にした。

* [APNG Specification](https://wiki.mozilla.org/APNG_Specification)

---

</br>

簡単に使えるようにしてみたつもりだがどうだろうか。
しかしヘッダーオンリーは便利だ。もう dll地獄だとか、サイドバイサイドがなんたらとかはうんざりだ。