today 2019-06-11
access_time 2 mins
私的にはgo言語入門
今回はPNGファイルを自力で読み込んで表示することができたので紹介する。ただ本命はPNGをデコードしたことではなく、これをgo言語で実装したことにある。
やったーー!!!golangで作った自作Animation PNG Decoderでアニメーションが表示できた!(背景の水色は合成)
— kamiya (@kamiya_owl) June 10, 2019
ぞうさんは https://t.co/4Eyy5zvsJ8 からお借りしました。 pic.twitter.com/qZWa4VoDCa
上記ツイートを見てもらえばわかるが動いている。これはAnimated PNGというアニメGIFの上位互換を目指したようなPNGの拡張規格である。せっかくGUIを出しているのでこのAnimation PNG(apng)をデコードしてみる。
モチベーションは以下にある。実は前にbackendで使ったことがあったが理解がなくinterface地獄になったこともあって個人的印象はあまり良くない。
偏見は良くないので以下の点を触って確かめるべくという具合だ。触って確かめたのであまり文書にできる感想はない。どちらかといえば良かったと思う。
先に示しておく
os.File
を利用する。見ればわかる単純明快さだ。(あとから知ったがio
パッケージがあったらしい)
defer句はリソース破棄時に実行されるようだ。ここではfileハンドルを捨てている。c#でいうところのusing(){}
らしい
golangはさておき早速実装を始めていく。詳細は記載しないので興味のある人やちゃんと定義を知りたい人はW3Cの資料を参照してほしい。
まず最小限の構成を示す。golangでの実装についてはGithub - kamiyaowl/Animation-PNG-Viewer apng.goのParse()を参考にしてほしい。
名前 | 内容 | 補足 |
---|---|---|
header | {0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a} | マジックナンバー |
IHDR | Width, Height, BitDepth, ColorType等 | 画像自体の構成情報 |
IDAT | zlib圧縮を施された画像データ | 先頭1byteはフィルタの種類を示す(後述)。またIDATは分割可能 |
IEND | - | 内容なし |
ヘッダはファイルを識別するためのものなので単純一致を調べれば良い。
他についてだが、PNGファイルはchunkと呼ばれる単位で分割されている。その中身は以下のようになっている。
名前 | 長さ | 内容 |
---|---|---|
Length | 4byte | Chunk Dataのbyte数 |
Chunk Type | 4byte | acsii charでchunk名(IHDR , IDAT 等) |
Chunk Data | 0~Length byte | chunkのデータ本体 |
CRC | 4byte | ChunkType~ChunkDataのCRC32(生成多項式はISO-3309) |
ここで言うChunkTypeは、先頭二文字が大文字は必須chunkで小文字だと補助(必須ではない)chunkである。また、PNGの2byte,4byteデータはBigEndianなので気をつけてほしい。
手順としてはこうだ。
これをループさせて、IHDR
, IDAT
が読み出すことができれば元通りの画像を作ることができる。
ChunkDataをIhdr構造体にばらした。
特にやることはない
Interlace対応をひとまずおいておくなら(全部読み切ってから表示するなら)以下の実装で構わない。Idatのデータを連結しているだけである。
IHDR, IDAT, IENDを読み出したところでデータをもとに戻す。手順は以下の通り。
1.についてはgoのライブラリを使って展開した。自力でやることも当然可能だ。 2.はフィルタリングについて解説する。
zlib圧縮(しいてはDeflate)は、いわゆるハフマン符号化なので同じ符号列が頻出したほうが圧縮率が良い。(いろいろ語弊があって怒られそう) 何がしたいかというと、
こうなると圧縮に有利である。今の例はUpフィルタと呼ばれている。実際には以下の種類が定義されている。
種類 | 説明 |
---|---|
None | フィルタリングなし |
Sub | 左Pixelとの差分 |
Up | 上Pixelとの差分 |
Average | (上Pixel+左Pixel)/2との差分 |
Paeth | 割愛 |
Paethフィルタはさておき割と簡単に実装できる。(上Pixelと左Pixelで近い方の色を使う、ような実装になっている) 各行の先頭1byteにフィルタの種類が定義されているのでこれを読み出して、もとのbitmapに戻せば完了である。
ここまでで元通りの画像が表示できる。ちなみにguiにはfaiface/pixelを使っている。非常に良い
実はここまで実装できていればもう一息である。acTL
, fcTL
, fdAT
の補助チャンクが追加される。
名前 | 内容 | 補足 |
---|---|---|
header | {0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a} | マジックナンバー |
IHDR | Width, Height, BitDepth, ColorType等 | 画像自体の構成情報 |
acTL | アニメーションのフレーム数, 再生回数 | IHDR-IDAT間にあることが必須 |
fcTL | Width, Height, OffsetX, OffsetY, 表示時間、描画方法等 | fdATの画像情報を持つ。IDAT前にある場合はIDATがfdATの最初の画像とする |
IDAT | zlib圧縮を施された画像データ | 先頭1byteはフィルタの種類を示す(後述)。またIDATは分割可能 |
fcTL | SequenceNumber, Width, Height, OffsetX, OffsetY, 表示時間、描画方法等 | - |
fdAT | SequenceNumber, IDATと同様にデータ | 必ず手前にfcTLがある必要がある |
fcTL | … | … |
fdAT | … | … |
… | … | … |
IEND | - | 内容なし |
差分はこうだ。
IDATと同じフォーマットの画像データがfdATという補助チャンクで繰り返し出現するだけである。 シンプルな機能追加だと思わせているが、以下の特徴に注意する。
これらを失敗すると、おもしろ画像が生成される。
正しく実装できれば、冒頭のアニメーションが再生できるようになる。
Animated PNGもだが、PNGのフォーマット自身がかなり扱いやすいものだと実感した。
Go言語に関しては多相型がなかったり三項演算子がなかったり、今どきの言語に限らず目につくところはいろいろあるが、あとからコードを見返したときになんとなく何をしているのか読めるのが良い点だと感じる。 少なくともバイナリデコードするようなbetter cの用途においては十分すぎる簡潔さであると感じる。(Rustのほうが…とも思うが)
組み込みに関してはもう少し調査が必要である。