LoginSignup
8
7

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-04-15

APNG をC++11ヘッダーオンリーでデコードしてみたというお話。

apng

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

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

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

この uc_apng_loader.h 1つで・・・と言いたいところだが、別途 stb_image.h が必要だ。
しかし、ヘッダーオンリーには違いなかろう。

サンプルコード

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

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;
}

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

$ 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

APNG は PNG と互換性がある。
非対応のデコーダが普通に PNG として読み込めるように作られている。
その場合に表示されるフレームが デフォルトイメージ2

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地獄だとか、サイドバイサイドがなんたらとかはうんざりだ。


  1. これがフレームごとに指定されているということは、仕様上はフレームごとに表示時間が変わっても良いということになる。私は見たことないが。 

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

8
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
7