APNG をC++11ヘッダーオンリーでデコードしてみたというお話。
↑ 対応ブラウザなら動いて見えます。
仕様自体は結構前からあるらしいが、最近 LINE の動くスタンプだの、Chromium で表示できるようになっただので、にわかに注目されている気がする。
このデコーダを C++11 で実装して github で公開してみた。
この uc_apng_loader.h
1つで・・・と言いたいところだが、別途 stb_image.h が必要だ。
しかし、ヘッダーオンリーには違いなかろう。
サンプルコード
デコード結果を stb_image_write.h
を使って PNGファイルとして連番保存するコードは以下のように書ける。
#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;
}
ヘッダーオンリーなのでライブラリのリンクは要らない。
$ g++ -std=c++11 apng2pngs.cpp
これだけでOK.
主な登場人物は以下の3人。
loader
frame
image
uc::apng::loader
loader
は以下のように作成する。
// ファイルから読み込む
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()
で取得できる。
以下のようにまとめて取得しても良い。
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
に格納される。
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。
uc::apng::image_t
uc::apng::image_t
は、デコード済み32ビットRGBA画像を格納する。
画像サイズは image_t::width()
, image_t::height()
で、RAWデータは image_t::data()
で取得できる。
例えば OpenGL でテクスチャに渡す場合はこうだろう。
// 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仕様についてはここを参考にした。
簡単に使えるようにしてみたつもりだがどうだろうか。
しかしヘッダーオンリーは便利だ。もう dll地獄だとか、サイドバイサイドがなんたらとかはうんざりだ。
-
これがフレームごとに指定されているということは、仕様上はフレームごとに表示時間が変わっても良いということになる。私は見たことないが。 ↩