STM32F4シリーズを使ってみる5 -libpngを実装する-


誘惑に負けて買ってしまった…盈钰電子のSTM32F4ボード…
176pinなSTM32F407IGT6,1MBSRAM,NAND&NORFlash,HS-USBPHY,Ethernet,I2SCodec,camera....
そして3.2inchのTFT-LCDまでついてオトクな699RMB!
日本円単純換算でも10000円以下の超格安です(2012/5上旬現在)

本来ならばREDBULLの基板をベースにやるべきですが費用対効果が最高なので購入した
方が早いっちゅーことで…しかもSTM3240G-EVALの回路にほぼ互換なのでSTさん提供
のサンプルなんかもほとんど素で動かせられますのでこいつでいろいろ学習していき
ましょ♥
だめだ私すっかりSTM32漬けだ…





さて、前回さらっとご紹介しましたが、libpngのSTM32F2/4への移植についてお伝え
します。私たち虹メにとってpng形式の画像ファイルはコラ等で非常に身近なもので、
jpeg形式と同じく大量に扱われています。それをマイコンでデコードし表示できる
というのはとても有用性があるわけで、かねてからの悲願がかなった形でもあります。
今回は実際の移植の手順も合わせてご紹介します。


●必要なもの
libpng
これは必須ですね。少し前にlibpngのセキュリティホールが発見されているためバー
ジョンには気を付けてください。今回は"libpng 1.5.10"を使用します。

zlib
pngファイルの実画像データは可逆圧縮されていてDeflateと呼ばれる圧縮技術が使用
されています。libpngはDeflateアルゴリズムを処理するためそのライブラリである
zlibの一部コードを必要としています。今回は"zlib 1.2.6"を使用します。

MCU
libjpeg(IJG Jpeg Library)と同じくlibpngはRAMを大量に消費します。
内部SRAMが96kByte以上あるマイコンならQVGAサイズまでなら何とか表示可能です。


●ファイルI/O
こちらはlibjpeg(IJG Jpeg Library)と同じ要領でChaN氏のFatFsと連動させます。
sirius506氏、そら。氏のlibjpegの移植例を参考にしています。
実際にどうやってるかは私のサンプルの./lib/display/abstract/src/ts_fileloads.c
にあるload_png関数の上にあるfatfs_read_data()を読み進めてください(丸投げ!)


●makefileに追加するCソースファイル
こちらのSTM32F4サンプル中にあるlibpng.mkを見ていただければわかりますが、
pngファイルの読み取りのみに限ると以下のファイルだけが必要となります。
-libpng
png.c
pngerror.c
pngget.c
pngmem.c
pngpread.c
pngread.c
pngrio.c
pngrtran.c
pngrutil.c
pngset.c
pngtrans.c

上記のうちpngerror.cは一部内容の書き換えが生じます(後述)

-zlib
adler32.c
crc32.c
inffast.c
inflate.c
inftrees.c
zutil.c

ヘッダファイルの方については念のためオミットせずに全部ぶっこんどいてください。
また、libpngを解凍したディレクトリ中の./scripts/pnglibconf.h.prebuildをリネ
ームしpnglibconf.hとしてソースと同じディレクトリにぶっこんでください。

●ARMマイコン上で動作させるための少しの変更
x86なWindowsシステムで動作させるわけではないため、エラー表示などのコンソール
I/Oの処理のオミットは必須です。
pnglibconf.h中のPNG_CONSOLE_IO_SUPPORTEDとPNG_STDIO_SUPPORTEDの
定義はコメントアウトしておき、さらにpngerror.c中のpng_default_error
関数はfprintfからprintfに変えておきます。
(printfのリダイレクトが必要無い場合はすっからかんでもいいです)
ねむいさんの環境では書き換えたファイルはpngerror_std.cとしています。

20140731追:
↑バージョンが上がってヘッダの#defineをコメントアウトすればよくなりましたので
 pngerror.cに手を加える必要がなくなりました。

また、txtとハードウエアの倍精度浮動小数点演算、インターレースのサポートも
行わないのでpnglibconf.h中の以下の定義もコメントアウトです。
PNG_READ_INTERLACING_SUPPORTED
PNG_FLOATING_ARITHMETIC_SUPPORTED
PNG_WRITE_zTXt_SUPPORTED
PNG_WRITE_iTXt_SUPPORTED
PNG_READ_zTXt_SUPPORTED
PNG_READ_iTXt_SUPPORTED
PNG_zTXt_SUPPORTED
PNG_iTXt_SUPPORTED
元からコメントアウトされていたものについてはそのままにしておいてください。

さらにpnglibconf.h中のZBUFFERの確保量(PNG_ZBUF_SIZE)をSDカードのセクタ
サイズである512バイトに合わせてください。これ以上の
値にすると512バイト以上読み込んだ際にエラーになりデコード不可能になります。
また、残念ながらlibpngにはlibjpegのような1/(2^x)にするサイズスケーリングは
ありません。


●libpngを呼び出し、画像データを表示する

大まかに分けて以下の順番で実行します。これらは必須です。
1.png_create_read_struct関数でpngファイル全般を制御するポインタ確保
2.png_create_info_struct関数でpngファイルの情報に関するデータをストアする
  ポインタ確保(IHDRとIENDチャンクの分)
3.エラーハンドリングの設定
4.png_set_read_fn関数でfatfsのpngファイルポインタと連結
5.png_get_IHDR関数でIHDRチャンクの読み出し
6.画像の形式を24bitRGBに変換する。
7.画像のx,yサイズのチェック(1280pixel以上は撥ねる)
8.png_get_rowbytes関数で1行あたりのバイト数取得
9.png_malloc関数で"6."で取ったバイト数分の配列確保
10.png_read_row関数でIDATチャンクを一行ずつ読み出し、さらにRGBデータ幅を24bit->16bit
  に落としてTFT-LCDに表示していく
11.表示が終わったらpng_free関数"7."で確保した配列を解放
12.png_read_end関数でIENDチャンクを読みだす。
13.png_destroy_read_struct関数で"1.""2."で確保したポインタの解放

気を付けなければならないのは各処理ごとにエラーを検知しておかしい場合は先の
処理に進ませないようにすることです。これを怠るとHardFaultに簡単に陥ってし
まいます。またIDATを読み切ってない場合にpng_read_endを実行すると即座にエラ
ーになりますのでTFT-LCDの解像度より画像サイズが大きい場合でも必ず空読みして
読み切ってください。この手順は24bitのpngを想定していますが異なる形式の場合は
24bitに変換するために"5.""6."の間に各種transformの処理が必要です。
実際にどうやってるかは私のサンプルの./lib/display/abstract/src/ts_fileloads.c
にあるload_png関数を読み進めてください(丸投げ!)

一回の表示で消費されるRAMはlibpng自体が必要とする量が約33kByte(デバッガの
追跡により使用量を算出),それに加えてTFT-LCDに表示する際のデコード済みの
データを置くバッファも必要とされるため結局以下の計算式で総消費量が求められます。
NEEDED_RAM_AMOUNT = ((XSIZE-1)*4)Byte+33kByte

式中のXSIZEはTFT-LCDのX軸方向最大ピクセル数ではなくpng画像データのX軸方向
最大ピクセル数です。つまり幅が広くなればなるほど使用するRAMの量が増えます
(libjpegもこの点は同じ)。また、今回のデモはアルファチャンネルを強制付与して
いるため(RGBAのならびになるため)(XSIZE-1)*3ではなく((XSIZE-1)*4)になります。


●実際の表示

今回の検証用のTFT-LCDとして320x480なTFT1P2797-Eを使用します。大規模マイコン
用のディスプレイとしてねむいさんのイチオシです♥


検証用の画像はこちら。こちらのサイトのキャラクタージェネレーターを使って作成した画像に
さらにコラージュを加えた私服のいないさんです。メイド服にコラしたかった
のですが髪型だけで力尽きましたorz…これをTFT-LCDに表示してみます。
ビット深度は24bitのトゥルーカラーです。

表示ルーチンとしてまず画面左上に画像の情報を出すようにしました。
col_depthはビット深度で8*(RGB)で上記の24bitとなります。
col_typeはpngの画像タイプで0がグレイスケール、3がインデックスカラー、
2がトゥルーカラー、6がアルファチャンネル付トゥルーカラーです。

実際の表示に関し、グレイスケールやビット深度が小さい画像も24bitに変換してい
ます。また透過pngやアルファチャンネルつきの物はいったんアルファチャンネルに
変換して、1ピクセルごとのアルファチャンネルを示すバイトは書き込み時に無視して
います。インターレースpngの表示はは私のサンプルではサポートしていません。
誰か腕に覚えのある方実装おねがします…。


てわけでPCとまったく遜色ないレベルでいなちゃんを表示することができました!
メモリが豊富な環境ではpng_read_image関数で一気に読み込みできるのですが、STM32
等のマイコンでは到底足らないので、1行ずつバッファに読みだして表示を行います。
読みだしたデータはRGBの順番で並んでいて8+8+8=24bitで1ピクセルとなります。
最終的にTFT-LCDには5+6+5=16bitにして書き込み、表示させます。


透過PNGを表示させるとこんな感じになります。前述の通りアルファチャンネルは
読み捨ててるので透過部分はRGB(0,0,0)の黒色で表現されています。また解像度の
関係で表示が隠れてしまったのですが、アルファチャンネル付トゥルーカラーに
変換された透過pngを示しています(赤で囲った部分)。


TFT-LCDのサイズよりオーバーする場合の画像では当たり前ですが画面外のデータは
表示できません。また画面からはみ出ている部分もpng_read_rowで全部読み切ってし
まわないとエラーになったりHardFaultに陥ってしまうので注意です。
ChaN氏のbmp表示ルーチンを見習って大きい画像でもUARTからのコマンドで仮想的な
表示開始位置を変えて表示できるようにするのが今後の課題ですね〜

ちなみのこのaraiさんの描いたねむいさんの絵はアダルツなのでこのブログ的にも
見えてない部分は…ゲフンゲフフン失礼


減色とかグレースケールにした時の比較です(注:表示の際はすべて24bitのトゥルー
カラーに変換されています。左上の数値は変換前の情報です)。

ビット深度24bitトゥルーカラーの標準的なねむいさんです。
col_depthが8になってますが実際は8bit*3で24bitとなります。

256色のインデックスカラーはこんな感じに。

24bitのグレースケールはこんな感じ。

8bitに落とすとこんな感じ。

さらに4bitに落とすとこんな感じです。



というわけで通常のPCと同じようにpngファイルの表示もSTM32F4上で可能と
なりました。しかしながらメモリはかなり食いますので内蔵SRAMが豊富にある
STM32F4専用の機能ですね〜。
libjpegと絡めると空いた時間でお茶が飲めてしまうくらいビルド時間も
倍加されてしまいますのでご注意ください。

Go to top of page