APNG(Animated PNG) is 何?
Animated PNGとは、その名の通り、PNGをアニメーション(連番画像)のように表現した画像のことで、PNGフォーマットを拡張してできた画像のファイルフォーマットです。
LINEの動くスタンプなんかは皆さんに馴染み深いのかなって思います。
実際に開発している、ライブラリは以下から飛べるので是非ご意見をいただければと思います。
https://github.com/poccariswet/apng <- 飛んでみてね🏃♂️
メリット of APNG
- 環境がサポートされていなくても、APNGの1フレーム目を普通のPNG画像(静止画)として使用できる。
- 可逆圧縮方式のため、劣化せずにデータを圧縮できる。
- アルファチャンネルもサポートするため、見た目が劣化せずにきれいに見える
上記のようなことがあげられます。
また、逆にデメリットとしては、サポートされているサービス、環境が少ないことです。
Why Rust???
最近、rustでwasmという記事や、rustの良記事(これとかこれ)を読んで、これやりたい!って思い、rustの勉強をし始めて、その勉強がてら作ろうと思いrustにしました。
Why APNG library???
なんで、APNG のライブラリかというと、いつも一緒に開発している友人のdennougorillaが、共有した画面をgifにclipするWebアプリをdart作っており、gifか〜じゃあ俺はAPNGのWebアプリを作ろうと言ったような割と適当な動機ですw
Webアプリを作る上でRustでapngのライブラリを探したんですが、自分がこれと言って使いたいライブラリがなく、ないならじゃあ作ろうということで作成しました。ちなみに、ライブラリと言っても、エンコーダーしかまだ対応していないです🙇♂️
実装
実装していくにあたり、APNG, PNGの必要な知識をここで吸収しました。実際に構造や手順などが詳細に載っているので興味がある人は読んでみてください😁
以下に簡単に要約を残しておきます。
APNGの構造
構造自体はPNG仕様と同様、そこにanimation(連番)を追加するためのチャンクを追加して、フレームデータを規定する。
- acTL(Animation Control チャンク): APNGのフレーム数、ループ回数を定義する。最初の
IDAT
チャンクの前に存在しなければいけない - fCTL(Frame Control チャンク): 1つのフレームに対しての情報(シーケンス番号やそのフレームを表示する時間)などを定義する。
IDAT
またはfdATチャンクの前に存在する。 - fdAT(Frame Data チャンク):
IDAT
と同じデータチャンク。シーケンス番号が最初に入っている以外は何も変わらない
元々のPNG仕様+上記の3つのチャンクがAPNGの構造になります。
図に示すとこんな感じです。
rustで表現
rustでは、APNG のEncoder
をnew
するときに、PNG SignatureからacTLまでを定義しておきます。
それぞれの処理に関しては、こちらのリンクから見てください
impl<'a, W: io::Write> Encoder<'a, W> {
pub fn new(writer: &'a mut W, config: Config) -> APNGResult<Self> {
let mut e = Encoder {
config: config,
w: writer,
seq_num: 0,
};
Self::write_png_header(&mut e)?;
Self::write_ihdr(&mut e)?;
Self::write_ac_tl(&mut e)?;
Ok(e)
}
…
その後に、PNG画像を上記の画像で示したように連番にエンコードしていきます。
// all png images encode to apng
pub fn encode_all(&mut self, apng: APNG, frame: Option<&Frame>) -> APNGResult<()> {
for v in apng.images.iter() {
if self.seq_num == 0 {
Self::write_fc_tl(self, frame)?;
Self::write_idats(self, &v.data)?;
} else {
Self::write_fc_tl(self, frame)?;
Self::write_fd_at(self, &v.data)?;
}
}
Self::write_iend(self)?;
Ok(())
}
簡単にエンコード処理を説明すると、write_fc_tl
で1フレームの定義に必要な描写領域やフレーム速度などバッファ(Vec<u8>
)に書き込み、write_chunkで、new
で定義した、writerに書き込みます。
また、write_fd_at
とwrite_idats
では、ZlibEncoderを使用し圧縮処理をして、圧縮処理後のバイナリをwriterに書き込んでいます。
⚠ 実装上、まだフレームごとに描写領域の設定や、フレーム速度をいじれないので今後対応していく予定。(2019/10/17現在)
ちょっと大変だったところ
実装していくにあたり、rust特有の機能である、所有権システムで、つまずきました😅
圧縮のために使ったZlibEncoderにデータを入れるためのbufferをアドレスで所有権渡していて、Zlib ライブラリの仕様上、finish
関数を最後に呼ばないと、その所有権が戻らないためエラーが吐かれていました。
error[E0502]: cannot borrow `buf` as immutable because it is also borrowed as mutable
--> src/main.rs:170:26
|
160 | let mut zlib = ZlibEncoder::new(&mut buf, Compression::fast());
| -------- mutable borrow occurs here
...
170 | self.write_chunk(&buf, *b"fdAT")?;
| ^^^^ immutable borrow occurs here
...
173 | }
| - mutable borrow might be used here, when `zlib` is dropped and runs the destructor for type `flate2::z
lib::write::ZlibEncoder<&mut std::vec::Vec<u8>>`
exampleにもちゃんと書いてありますね💦
なんでfinish
忘れたんだろう...
・Each value in Rust has a variable that’s called its owner.
・There can only be one owner at a time.
・When the owner goes out of scope, the value will be dropped
rustを書くなら覚えましょう
実装するときに使ったモロモロ...
インプットするときに自分が重宝したのは、メモ+小記事にもなり得る https://scrapbox.io です。
あとは、Rust自体勉強しながらだったので、rustのチュートリアルは本当に参考になりました。
以下に自分のまとめたscrapboxとrustチュートリアルのリンクがあります。
最後に
今後は自分が作ったapngのライブラリを使ってapng maker on wasmを実装していくのと同時に、rustを書いてる人に使っていただけるようにもっとチューニングしていければと思います!
作成したAPNG↓ ちなみにrustのlogoです
あ、QiitaってAPNG対応していたんですね...