2015年12月21日

ESP8266でPNGデコーダを実装してみた(4)

世間はすっかりクリスマス&スターウォーズに染まっていますね。
今週も週末は慌しく過ぎました。
今年は年末年始の休みも一週間しかなく、欲求不満が溜まりそうです。

今日は、PNGデコーダの実装に透過色を追加しました。

透過色は、「tRNS」チャンクで定義します。
中身は色ごとのアルファ値ですが、色の表現自体がカラーモードによって異なりますので、tRNSチャンクの中身もカラーモードによって異なります。
今回実装したのはインデックスカラーモードでの透過色だけです。
RGBモードでも、tRNSチャンクで色ごとにアルファ値を設定することができますが、まだ実装していません。

透過色の指定は圧縮・伸張とは関係が無く、描画時の処理がメインです。
デコーダライブラリではtRNSチャンクのデータの保持と参照を行います。
それを解釈して描画するのはデコーダライブラリを利用する側の仕事です。
今回はArduino上で描画する都合上、アルファ値の解釈は透明(0)・不透明(255)の2種類のみ行いました。

PNGに透過色を指定するツールとして、TweakPNGというツールを使いました。

【レビュー】PNG画像を解析して詳細なフォーマット情報を取得・表示するツール「TweakPNG」 - 窓の杜

このツールでPNGファイルを読み込み、tRNSチャンクを追加すると、透過色を指定することができます。

透過色ありPNGを表示させてみた様子が以下の動画です。
透過色対応させる場合は、高速なpushColorが使えませんので、少し遅くなりますが、透過色ピクセルを描画していないので見かけ上はそれほど遅くはなっていません。



この動画では、市松模様を表示してから、透過有りのPNGを表示させています。
市松模様を動画圧縮しているせいで、かなりひどい画質になっていますが・・・。
posted by boochow at 02:14| Comment(0) | ESP8266 | このブログの読者になる | 更新情報をチェックする

2015年12月14日

ESP8266でPNGデコーダを実装してみた(3)


この週末はあまり時間が取れなかったのですが、このところ作っているPNGデコーダを、1bit/2bit/4bitのインデックスカラーに対応させてみました。

動画の通り、インデックスカラーはそれなりに高速に表示できます。
動画では、1bitの画像は表示させている画面がノイズのように見えますが、実は以前プチコン3号で作成した迷路の画像です。

このデコーダーの開発は、もっぱらVisual Studio上で行い、最後にArduino IDEで描画部分だけ追加しています。
やはり、少しプログラムが複雑になってくると、変数の状態を観察しながらステップ実行できる環境は欠かせないですね。
posted by boochow at 01:34| Comment(3) | ESP8266 | このブログの読者になる | 更新情報をチェックする

2015年12月09日

ESP8266でPNGデコーダを実装してみた(2)


前回実装したPNGデコーダに、インデックスカラーモードとグレースケールモードの実装を追加してみました。
PNGの画像圧縮・伸長はバイト列が対象なので、カラーモードごとの違いはデコード結果のバイト列をどう解釈するかだけの違いです。

PNGはGIFより遅いなと思っていたのですが、インデックスカラーではそこそこの速度でした。
フルカラーだと1ピクセルあたり3バイトをデコードする必要がありますから、その場合は遅くなるのは当たり前でした。


ところで、先月末にESP8266用Arduinoがメジャーバージョンアップしていました。

esp8266/Arduino ・ GitHub

早速インストールしてみたのですが、なぜかILI9341ドライバが使えなくなっていたため、現在は古いバージョンに戻しています。
HTTPClient libraryが追加されたようなので、気になっているのですが・・・。
posted by boochow at 00:07| Comment(0) | ESP8266 | このブログの読者になる | 更新情報をチェックする

2015年11月24日

ESP8266でPNGデコーダを実装してみた

esp826613.jpg

前回、「ESP8266でオンメモリでPNGをデコードするのは難しいかも」と書きましたが、あれこれ考えた結果、ぎりぎり何とかなりそうな見通しが立ったので、この3連休を使って実装してみました。

Deflate圧縮の伸張には、前回紹介した中から「tiny inflate」を選びました。

jibsen / tinf − Bitbucket

PNGファイル自体の扱いは、主に以下を参考にしながら自力で書きました。

apankrat/lpng
PNG画像を自力で読む
Portable Network Graphics (PNG) Specification (Second Edition)

懸念点だったメモリについては、グローバルメモリに32KBのリングバッファと、スキャンライン2本分のバッファを用意しました。
前者はLZ77圧縮の復号、後者は復号後のフィルター処理にどうしても必要となりますが、これだけで
グローバル変数が 72,814バイト (88%) の 動的メモリを使用しており、ローカル変数に 9,106 バイトが残っています。最高は 81,920バイトです

という状態になっており、かなり苦しい感じです。

描画速度もGIFより遅いので、フルカラーが使えるという以外にはあまりメリットは無いかもしれません。
posted by boochow at 02:26| Comment(0) | ESP8266 | このブログの読者になる | 更新情報をチェックする

2015年11月08日

PNGデコーダの調査結果:ESP8266でのデコードは厳しいかも

ESP8266でJPEGGIFがデコードできましたので、PNGも…と思い、仕様や既存のライブラリを調査していました。
その結果をここでまとめておきます。

結論としては、ESP8266でのPNG画像のデコードは不可能ではないものの、RAMの消費量や現時点で使えるデコーダライブラリの状況からは、JPEGやGIFに比べると課題が多いと言えます。

まず、PNGの圧縮方法について触れておきます。
PNGは、原理的には「ビットマップ画像をZIPで圧縮した」イメージに近いです。
圧縮アルゴリズムはDeflateと呼ばれているもので、ZIP、gzip等で使われているものと同じです。
Deflate圧縮は、出現頻度の高いビット列に短い符号を割り当てるハフマン符号化と、過去に既出のビットパターンを参照することでデータの繰り返しを圧縮するLZ77を組み合わせたものです。

Image compression
Deflate
SWFバイナリ編集のススメ番外編 (zlib 伸張) 前編 | GREE Engineers' Blog
SWFバイナリ編集のススメ番外編 (zlib 伸張) 中編 | GREE Engineers' Blog

次に、Deflate圧縮の特徴ですが、過去に既出のパターンがあったかどうかを調べるための辞書が必要となります。
どれくらい過去までさかのぼるか(スライディングウインドウ)で辞書のサイズが決まってきますが、PNGではこのサイズは最大32768となっています。
従って、デコード側では32768バイトのデータを保持しておく必要があります。
これだけでRAMのデータ用領域を半分近く消費してしまいますので、ESP8266のメモリ内でのデコード処理はなかなか困難です。
現実的にはファイルなどに書き出して過去のデータを参照可能にしておく必要がありそうです。

また、PNGは単に画像をDeflate圧縮するのではなく、スキャンライン単位で前処理を適用することで、圧縮率を高めています。
前処理は可逆変換で、ピクセル間の差分のみを記録する方法が4通り用意されています。

PNG画像を自力で読む

この前処理は、デコード時に逆の処理を行って元の画像を復元しなければなりませんので、最低でも1スキャンライン分は、画像をバッファしておく必要があります。

ということで、ESP8266でオンメモリでPNG画像をデコードするのは困難ではないかと思われます。
SPIFFSなどの外部記憶を使う必要があるでしょう。


次に、既存のPNGデコーダを調べてみました。

標準のデコーダはlibpngで、Deflate圧縮にはzlibを使用しています。

PNGをデコードするのに必要なファイルだけをlibpngとzlibから抽出し、ARM系のマイコンであるSTM32F4で動作させた例が以下で紹介されています。

ねむいさんのぶろぐ | STM32F4シリーズを使ってみる5 -libpngを実装する-

libpng+zlibはESP8266で使うにはやや大規模すぎます。
zlibのcontribフォルダに、puffというより小規模なDeflateの実装があります。
puffはブートローダなど、より制約の厳しいソフトウェアで使うことが想定されています。
ただし、提供されているAPIはデータを一度に全て解凍しますので、画面全体を納めるバッファが必要です。
組み込みで使うには、漸進的に解凍するような処理を自分で書く必要があるでしょう。

zlib/contrib/puff at master ・ madler/zlib

puffはzlibよりもスピードは遅いですが、これでPNGデコーダを作ることもできます。
そのようなデコーダの例としてlpngがあります。
ただし、puffをそのまま使っていますので、復号後の画像を保持できるだけのバッファが必要になります。

apankrat/lpng

zlib以外のDeflateアルゴリズムの実装、特に伸張部分のみの実装の一覧がWikipediaにあります。

DEFLATE - Wikipedia, the free encyclopedia

この中で、組み込み向けと思われるものにminizがあります。
画像をPNGファイルに書き出す関数は組み込まれていますが、PNG画像を展開する関数は実装されていません。

miniz - Single C source file Deflate/Inflate compression library with zlib-compatible API, ZIP archive reading/writing, PNG writing - Google Project Hosting

tinf(Tiny INFlate)も小さなDeflate(からの解凍)の実装です。
他のものと同様、圧縮されたビット列と展開したビット列がオンメモリであるという前提の実装です。
ただ、これくらい短いコードだと、漸進的に解凍するように変更することも容易かもしれません。

jibsen / tinf − Bitbucket

zlibに依存しないPNGデコーダ・エンコーダ実装としては、LodePNGがあります。
内部的に、メモリを確保するためにmallocを使用していますので、そのままでは小規模な組み込みシステムで使用するのはちょっと苦しそうです。

LodePNG

LodePNGのページの中に、さらに小規模なデコーダ実装であるpicopngも紹介されています。
こちらは、メモリを確保するのにSTLのVectorを使用しています。
STL自体は、Arduinoにも移植されていますが、picopngを使用しても、一時的に大量のRAMが必要になるという問題自体の解決にはなりません。
picopngはメモリのフットプリントが小さいわけではなく、1つのファイルに短くまとまったポータブルなデコーダと見るべきで、必ずしも組み込みに向いているわけではなさそうです。

lodev.org/lodepng/picopng.cpp
maniacbug/StandardCplusplus

LodePNGはエンコーダも含んだライブラリですが、デコーダ部分だけを抜き出したupngという派生系があります。
こちらはSTLも必要なく、picopngよりも組み込み向きかもしれません。
とはいえワークメモリ確保にはmallocを使っていますし、全体を一度にデコードするので一時的に大量のRAMが必要になる点も違いはありません。

elanthis/upng

他には、以下のページで日本の方がC++で作成されたエンコーダ/デコーダの実装が公開されています。
こちらは、入力データはバッファへ全て読み込み、デコードは1バイトずつ出力する形態になっています。

YSFLIGHT.COM - PNG Encoder / Decode in C++


という感じで、いろいろデコーダを眺めてみましたが、PNGのデコード処理が

 ハフマン符号の列(不定長の符号)→ピクセル列(復元前)→元画像の復元

というステップを辿るため、基本的にはこれらのステップを直列的につないだデコーダばかりでした。
そのため、元画像の復元前の段階で、画像全体をメモリ上に保持するような内部構造になっています。
実際には復元処理には、1つ上のスキャンラインを保持していれば十分なはずですので、上の3ステップは並列に動けるはずです。
libpngにはそのような実装の解説もされていますが、より小さなデコーダでProgressive Decodeを実装したものは見当たりませんでした。

Reading PNG Images Progressively (PNG: The Definitive Guide)

Deflate圧縮・伸張自体は、データを読みながら出力することは可能です(でなければ巨大なファイルを圧縮・伸張できません)ので、PNGの展開でも同様のことは可能だと思います。
次のリンク先のコードは、zlibを使った、読みながら出力するプログラムの例です。

https://oku.edu.mie-u.ac.jp/~okumura/compression/comptest.c

以上、結局ESP8266で使えそうなPNGデコーダは見つかりませんでしたが、もし実装するとしたら以下のような形態になるのではないかと思います。

・Deflateされたデータを解凍した結果は、一時ファイルに保存する。解凍処理時に32768個前までのデータを参照できることが必要で、ESP8266には十分なRAMが無いため。

・解凍したデータを表示する前に、差分情報から原画像を復元する。これは全データを解凍後に一時ファイルから復元しても良いし、解凍中に平行して行うこともできるはず。

オンメモリでデコードすることも不可能ではないと思いますが、実用性という点では疑問符が付きます。
PNGが登場したのは1990年代後半ですから、PCには既に数十MBのRAMが搭載されていました。
GIFやJPEGの登場時よりも、10倍くらい潤沢なメモリが使えたことになります。
さすがに、100KBに満たないRAMでデコードする場合のことはあまり考慮されていなかったのかもしれません。
posted by boochow at 00:38| Comment(0) | ESP8266 | このブログの読者になる | 更新情報をチェックする
人気記事