M5Stackとフォント GFXFFへの対応 [ESP32]
前回ではランレングスに対応しました。今回はGFX Free Fontと言うフォントの対応です。
GFX Free Fontもプロポーショナルフォントですが、特に筆記体を表示するのに向いている様な気がします。
マイコンにとって最も処理の負荷が小さそうなのは等幅フォントですが、等幅フォントでは例えば小文字の'i'が連続した時に、'i'と'i'の間が大きく開いて体裁が良くありません。
プロポーショナルフォントなら文字毎に適切な文字間隔を指定できるので、とても体裁が良くなります。しかし制御が増えるのでその点は良し悪しです。
ランレングスはSPIとは相性が良さそうな気がしますね。
GFXフォントの使い方の解説(本家?)はここから。
https://learn.adafruit.com/adafruit-gfx-graphics-library/using-fonts
Customフォルダーの中のフォントは以下のサイトで生成された物?
http://oleddisplay.squix.ch/#/home
GFX Free Font(GFXFF)を使う為のもっとも簡単な方法は、すでにM5ライブラリに用意されているメソッドを使う事です。
例えば
setFreeFont( GFXFFにアクセスする為の構造体のポインタ );でこれから使用するフォントを選択。
drawString( テキスト, 横位置, 縦位置, フォント番号 );で指定された位置に先に指定されたフォントでテキストを描画します。まあつまりこんな感じで、
これらメソッドはM5Stack.hには記載されていませんが、M5Display.hの中でTFT_eSPIクラスが継承されているのでIn_eSPI.hを参考にすると良いでしょう。
しかし相変わらずこのメソッドでは画面の右端に行った時に折り返しをしてくれません、、、よね?
折り返しとか改行とかを行いたいなら、自分で描画メソッドを作るしかない?
以下ではその解説を!
1.GFXフォーマットのフォントデータファイルは3つの要素で構成されている。
1) 文字をビットマップデータ化したデータ領域
Yelllowtail_32.hであれば
const uint8_t Yellowtail_32Bitmaps[] PROGMEM
配列名は任意でこのファイルの中でしか利用されないが、static宣言はされていない。
2) 該当文字の属性データ。例えば該当文字のデータが始まる位置を、上記ビットマップデータ
の先頭からのオフセットとか、文字の横サイズ、文字の縦サイズ、次の文字までのピッチ、
描画原点からの横と縦のオフセット等が配列化されている。
3) ビットマップデータ、上記属性データ、行間のピッチをまとめた物。
文字を表示する場合はこの変数をアクセスする事となる。
2.GFXフォーマット
gfxfont.hの中で2つの構造体が定義されている。
1) GFXglyph
Yelllowtail_32.hの中の文字'A'のGFXglyphレコード
{ 1141, 24, 24, 20, 1, -23 }, // 'A'
bitmapOffset : ビットマップデータ領域の先頭からのオフセット。この場合は1141。
width, height : 文字の横幅 = 24ピクセル。高さ = 24ピクセル。
xAdvance : 次の文字までの横方向のピッチ = 20
xOffset, yOffset : 文字の描画開始位置からの横方向のオフセット = 1。
文字の描画開始位置からの縦方向のオフセット = -23。
2) GFXfont
Yelllowtail_32.hの中のGFXfontレコード
const GFXfont Yellowtail_32 PROGMEM = {
(uint8_t *)Yellowtail_32Bitmaps,(GFXglyph *)Yellowtail_32Glyphs,0x20, 0x7D, 45};
bitmap : ビットマップデータ全体へのポインタ
glyph : GFXglyph構造体の配列のポインタ
first, last : 最初の文字、最後の文字。ASCIIコードからfirstを引いた値を
GFXglyph構造体配列のインデックスとする。
yAdvance : 次の行までの高さ。
3.GFXフォーマットの文字データの展開
該当文字のGFXglyph構造体レコードを取得すると、該当文字データの開始アドレスを
取得できる。
このデータはランレングス(RUN LENGTH)の様な圧縮は行われておらず、ビット列を
ビットマップとして画面上に展開する。しかしFont16.cの様な無圧縮のビットマップ
データをそのままメモリ領域に展開してしまうと文字サイズが大きい時は必要なメモリ
領域も大きくなってしまうので文字の描画開始位置を指定する事でメモリサイズの
増大を防いでいる。
例えば'A'であれば文字の上の辺りから描画開始しなければならないが、'.'であれば
随分下の位置からの描画開始となる。
以下はYelllowtail_32.hの文字'A'と文字'.'のデータである。
文字'A'のビットマップデータ
0x00,0x00,0x1E,0x00, 0x00,0x3F,0x00,0x00,
0x7E,0x00,0x01,0xEE, 0x00,0x03,0xDC,0x00,
0x07,0x9C,0x00,0x0F, 0x38,0x00,0x1E,0x38,
0x00,0x1C,0x78,0x00, 0x38,0x70,0x00,0x70,
0x70,0x00,0xE0,0xE0, 0x01,0xE0,0xE0,0x03,
0xC1,0xC0,0x7F,0xFF, 0xC0,0x7F,0xFF,0x80,
0x0E,0x03,0x80,0x1C, 0x07,0x00,0x38,0x07,
0x00,0x78,0x0F,0x00, 0x70,0x0E,0x00,0xE0,
0x1E,0x00,0xE0,0x1C, 0x00,0xC0,0x38,0x00, // 'A'
でかい!
文字'.'のビットマップデータ
0x7F,0xE0, // '.'
文字によって横幅、高さ、描画開始位置等の属性が異なり、文字を描画する為には
その情報にアクセスしなければならない。
文字'A'の属性データ
{ 1141, 24, 24, 20, 1, -23 }, // 'A'
文字'.'の属性データ
{ 431, 4, 3, 9, 2, -2 }, // '.'
文字'A'のxOffsetは1、yOffsetは-23。
文字'.'のxOffsetは2、yOffsetは-2。
xOffsetは文字開始位置からの相対位置、yOffsetはyAdvanceからの相対位置となる。
基本的にM5stackの描画は左上から右下方向に開始され、Xが大きくなれば右に、
Yが大きくなれば下に進む。
それぞれの文字の描画開始位置は横方向ならxOffsetから、
縦方向なら yAdvance + yOffset(注1)から開始する。Yelllowtail_32.hのyAdvanceは45である。
お試しプログラムは以下のリンク
https://1drv.ms/u/s!AgxfaDqma1yrhk6AY-NUsBh7pyXb
(注1) とか言いつつソースには高さ方向で調整が入っているじゃあないかい!
※TFT_eSPIクラスにはreadPixelメソッドが存在するので、ピクセルの読み出し可能か?と期待しましたが、見事にFFFFが返ってくるorz
GFX Free Fontもプロポーショナルフォントですが、特に筆記体を表示するのに向いている様な気がします。
マイコンにとって最も処理の負荷が小さそうなのは等幅フォントですが、等幅フォントでは例えば小文字の'i'が連続した時に、'i'と'i'の間が大きく開いて体裁が良くありません。
プロポーショナルフォントなら文字毎に適切な文字間隔を指定できるので、とても体裁が良くなります。しかし制御が増えるのでその点は良し悪しです。
ランレングスはSPIとは相性が良さそうな気がしますね。
GFXフォントの使い方の解説(本家?)はここから。
https://learn.adafruit.com/adafruit-gfx-graphics-library/using-fonts
Customフォルダーの中のフォントは以下のサイトで生成された物?
http://oleddisplay.squix.ch/#/home
GFX Free Font(GFXFF)を使う為のもっとも簡単な方法は、すでにM5ライブラリに用意されているメソッドを使う事です。
例えば
setFreeFont( GFXFFにアクセスする為の構造体のポインタ );でこれから使用するフォントを選択。
drawString( テキスト, 横位置, 縦位置, フォント番号 );で指定された位置に先に指定されたフォントでテキストを描画します。まあつまりこんな感じで、
M5.Lcd.setFreeFont( &Yellowtail_32 ); // Select the font M5.Lcd.drawString( "hello world.", 0, 0, 1 );
これらメソッドはM5Stack.hには記載されていませんが、M5Display.hの中でTFT_eSPIクラスが継承されているのでIn_eSPI.hを参考にすると良いでしょう。
しかし相変わらずこのメソッドでは画面の右端に行った時に折り返しをしてくれません、、、よね?
折り返しとか改行とかを行いたいなら、自分で描画メソッドを作るしかない?
以下ではその解説を!
1.GFXフォーマットのフォントデータファイルは3つの要素で構成されている。
1) 文字をビットマップデータ化したデータ領域
Yelllowtail_32.hであれば
const uint8_t Yellowtail_32Bitmaps[] PROGMEM
配列名は任意でこのファイルの中でしか利用されないが、static宣言はされていない。
2) 該当文字の属性データ。例えば該当文字のデータが始まる位置を、上記ビットマップデータ
の先頭からのオフセットとか、文字の横サイズ、文字の縦サイズ、次の文字までのピッチ、
描画原点からの横と縦のオフセット等が配列化されている。
3) ビットマップデータ、上記属性データ、行間のピッチをまとめた物。
文字を表示する場合はこの変数をアクセスする事となる。
2.GFXフォーマット
gfxfont.hの中で2つの構造体が定義されている。
1) GFXglyph
typedef struct { // Data stored PER GLYPH uint16_t bitmapOffset; // Pointer into GFXfont->bitmap uint8_t width, height; // Bitmap dimensions in pixels uint8_t xAdvance; // Distance to advance cursor (x axis) int8_t xOffset, yOffset; // Dist from cursor pos to UL corner } GFXglyph;
Yelllowtail_32.hの中の文字'A'のGFXglyphレコード
{ 1141, 24, 24, 20, 1, -23 }, // 'A'
bitmapOffset : ビットマップデータ領域の先頭からのオフセット。この場合は1141。
width, height : 文字の横幅 = 24ピクセル。高さ = 24ピクセル。
xAdvance : 次の文字までの横方向のピッチ = 20
xOffset, yOffset : 文字の描画開始位置からの横方向のオフセット = 1。
文字の描画開始位置からの縦方向のオフセット = -23。
2) GFXfont
typedef struct { // Data stored for FONT AS A WHOLE: uint8_t *bitmap; // Glyph bitmaps, concatenated GFXglyph *glyph; // Glyph array uint8_t first, last; // ASCII extents uint8_t yAdvance; // Newline distance (y axis) } GFXfont;
Yelllowtail_32.hの中のGFXfontレコード
const GFXfont Yellowtail_32 PROGMEM = {
(uint8_t *)Yellowtail_32Bitmaps,(GFXglyph *)Yellowtail_32Glyphs,0x20, 0x7D, 45};
bitmap : ビットマップデータ全体へのポインタ
glyph : GFXglyph構造体の配列のポインタ
first, last : 最初の文字、最後の文字。ASCIIコードからfirstを引いた値を
GFXglyph構造体配列のインデックスとする。
yAdvance : 次の行までの高さ。
3.GFXフォーマットの文字データの展開
該当文字のGFXglyph構造体レコードを取得すると、該当文字データの開始アドレスを
取得できる。
このデータはランレングス(RUN LENGTH)の様な圧縮は行われておらず、ビット列を
ビットマップとして画面上に展開する。しかしFont16.cの様な無圧縮のビットマップ
データをそのままメモリ領域に展開してしまうと文字サイズが大きい時は必要なメモリ
領域も大きくなってしまうので文字の描画開始位置を指定する事でメモリサイズの
増大を防いでいる。
例えば'A'であれば文字の上の辺りから描画開始しなければならないが、'.'であれば
随分下の位置からの描画開始となる。
以下はYelllowtail_32.hの文字'A'と文字'.'のデータである。
文字'A'のビットマップデータ
0x00,0x00,0x1E,0x00, 0x00,0x3F,0x00,0x00,
0x7E,0x00,0x01,0xEE, 0x00,0x03,0xDC,0x00,
0x07,0x9C,0x00,0x0F, 0x38,0x00,0x1E,0x38,
0x00,0x1C,0x78,0x00, 0x38,0x70,0x00,0x70,
0x70,0x00,0xE0,0xE0, 0x01,0xE0,0xE0,0x03,
0xC1,0xC0,0x7F,0xFF, 0xC0,0x7F,0xFF,0x80,
0x0E,0x03,0x80,0x1C, 0x07,0x00,0x38,0x07,
0x00,0x78,0x0F,0x00, 0x70,0x0E,0x00,0xE0,
0x1E,0x00,0xE0,0x1C, 0x00,0xC0,0x38,0x00, // 'A'
でかい!
文字'.'のビットマップデータ
0x7F,0xE0, // '.'
文字によって横幅、高さ、描画開始位置等の属性が異なり、文字を描画する為には
その情報にアクセスしなければならない。
文字'A'の属性データ
{ 1141, 24, 24, 20, 1, -23 }, // 'A'
文字'.'の属性データ
{ 431, 4, 3, 9, 2, -2 }, // '.'
文字'A'のxOffsetは1、yOffsetは-23。
文字'.'のxOffsetは2、yOffsetは-2。
xOffsetは文字開始位置からの相対位置、yOffsetはyAdvanceからの相対位置となる。
基本的にM5stackの描画は左上から右下方向に開始され、Xが大きくなれば右に、
Yが大きくなれば下に進む。
それぞれの文字の描画開始位置は横方向ならxOffsetから、
縦方向なら yAdvance + yOffset(注1)から開始する。Yelllowtail_32.hのyAdvanceは45である。
お試しプログラムは以下のリンク
https://1drv.ms/u/s!AgxfaDqma1yrhk6AY-NUsBh7pyXb
(注1) とか言いつつソースには高さ方向で調整が入っているじゃあないかい!
※TFT_eSPIクラスにはreadPixelメソッドが存在するので、ピクセルの読み出し可能か?と期待しましたが、見事にFFFFが返ってくるorz
M5Stackとフォント ランレングス(RUN LENGTH ENCODING)への対応 [ESP32]
前回ではランレングスのデコード方法が判らなかったので、非圧縮フォントデータのみ扱いましたが、今回はデコード方法が判ったのでそれに対応しました。
一応フォントデータのデコード方法ですが、例えばFont32rle.cを見てみると
widtbl_f32[96]と言う配列、chr_f32_20[]と言う配列、chrtbl_f32[96]と言う配列が有る訳ですが、
widtbl_f32はその文字の横幅を示しています。最初の値はASCIIコードでスペースなのですが、そのスペースの横幅はこの場合5ピクセルです。
chr_f32_20は文字のビットパターンで、ここがランレングスで圧縮されています。配列名の最後の2文字はASCIIコード番号を示し、この場合はスペースの20(16進)です。
chrtbl_f32は上記ビットパターンのポインターの配列です。
なので文字の横幅を知りたい時は、ASCIIコード番号からオフセット分を引いた値をwidtbl_f32配列のインデックスにすればアクセスできますし、ビットパターンを知りたい時はASCIIコード番号からオフセット分を引いた値をchrtbl_f32配列のインデックスにすればアクセスできます。
ランレングスのデコードですが、ビットパターンのそれぞれの1byte単位のデータに着目してみます。
例えばASCIIコードでスペースの値は以下です。
1byteのデータの最上位ビットが色の情報であり、例えば最上位に1が立っていれば1ピクセル表示する。1が立っていなければ1ピクセルの表示は行わないとします。(逆でも構わない)
続く7bitはその色情報が続く長さを示し、0から127までの値を取りえますが、実際はその値に1を加算します。
上記の0x7F, 0x1であれば、127+1+1+1=130ピクセル分表示しない事になります。
さて横幅は5ピクセルですので、130を5で割れば26となり、26は文字高さになります。
実際はこの値はFont32rle.hの中で定義されていますので、この定義を利用する方が良いでしょう。
さて、ランレングスのデコード方法が判ったので、実際にLCDに表示させてみました。
フォントサイズが32まではそれなりにASCIIコードが表示されますが、それ以上のサイズのフォントはまともに表示できるのは数字のみの様です。
ROMサイズをケチったのか?それとも面倒臭かったのか?
ESP32 Arduinoのプロジェクトを置いておきます。
https://1drv.ms/u/s!AgxfaDqma1yrhk6AY-NUsBh7pyXb
※追記
M5Stackの組み込みフォントのヘッダーファイル、例えばFont32rle.hは再帰呼び出しに対応していないので、ファイルの先頭と最後に以下の様な#ifdef文を追加します。Font32rle.hの例。
一応フォントデータのデコード方法ですが、例えばFont32rle.cを見てみると
widtbl_f32[96]と言う配列、chr_f32_20[]と言う配列、chrtbl_f32[96]と言う配列が有る訳ですが、
widtbl_f32はその文字の横幅を示しています。最初の値はASCIIコードでスペースなのですが、そのスペースの横幅はこの場合5ピクセルです。
chr_f32_20は文字のビットパターンで、ここがランレングスで圧縮されています。配列名の最後の2文字はASCIIコード番号を示し、この場合はスペースの20(16進)です。
chrtbl_f32は上記ビットパターンのポインターの配列です。
なので文字の横幅を知りたい時は、ASCIIコード番号からオフセット分を引いた値をwidtbl_f32配列のインデックスにすればアクセスできますし、ビットパターンを知りたい時はASCIIコード番号からオフセット分を引いた値をchrtbl_f32配列のインデックスにすればアクセスできます。
ランレングスのデコードですが、ビットパターンのそれぞれの1byte単位のデータに着目してみます。
例えばASCIIコードでスペースの値は以下です。
PROGMEM const unsigned char chr_f32_20[] = { 0x7F, 0x1 };
1byteのデータの最上位ビットが色の情報であり、例えば最上位に1が立っていれば1ピクセル表示する。1が立っていなければ1ピクセルの表示は行わないとします。(逆でも構わない)
続く7bitはその色情報が続く長さを示し、0から127までの値を取りえますが、実際はその値に1を加算します。
上記の0x7F, 0x1であれば、127+1+1+1=130ピクセル分表示しない事になります。
さて横幅は5ピクセルですので、130を5で割れば26となり、26は文字高さになります。
実際はこの値はFont32rle.hの中で定義されていますので、この定義を利用する方が良いでしょう。
さて、ランレングスのデコード方法が判ったので、実際にLCDに表示させてみました。
フォントサイズが32まではそれなりにASCIIコードが表示されますが、それ以上のサイズのフォントはまともに表示できるのは数字のみの様です。
ROMサイズをケチったのか?それとも面倒臭かったのか?
ESP32 Arduinoのプロジェクトを置いておきます。
https://1drv.ms/u/s!AgxfaDqma1yrhk6AY-NUsBh7pyXb
※追記
M5Stackの組み込みフォントのヘッダーファイル、例えばFont32rle.hは再帰呼び出しに対応していないので、ファイルの先頭と最後に以下の様な#ifdef文を追加します。Font32rle.hの例。
#ifndef FONT32RLE_h #define FONT32RLE_h #include#define nr_chrs_f32 96 #define chr_hgt_f32 26 #define baseline_f32 19 #define data_size_f32 8 #define firstchr_f32 32 extern const unsigned char widtbl_f32[96]; extern const unsigned char* const chrtbl_f32[96]; #endif /*FONT32RLE_h*/