電子ペーパーを使ってみる(その3)

電子―ペーパーシリーズの3回目です。
前回までで、Arduinoを使って、数字を表示するところまでできました。
alasixosaka.hatenablog.com

ただ、これでは数字があまりに小さいのでフォントを大きくしてみたいと思います。

巨大フォントを作る

この手のグラフィックスディスプレイを使うときに一番困るのがフォントをどうするかです。
Arduinoなら、有名なU8glibを使えば、結構色々なフォントが揃っているので、それほど困らないのかもしれませんが、最終的にはPICを使おうと思っているので、フォントを自前で用意してやる必要があります。
実は、ラジオを作っていた時も、有機ELディスプレイを使って、近畿の放送局くらいは漢字で表示しようと考えていたのですが、適当なフォントが見つからず結局そこで挫折して今日に至っているという状況です。
グラフィックスディスプレイで表示しようと思うとビットマップフォントが必要になってくるのですが、なんせPCの世界では(スマホでもそうですが)絶滅危惧種ですから、おいそれと自分に都合のいいフォントが転がっているわけはありません。無料で使おうと虫のいいことも考えていますので特にです。
ならば、自分で作ればということになるのですが、でっかいフォントをかっこよく作るのは結構難しい。今回の電子ペーパーであれば、128×296ドットのディスプレイですから、横長に使うとして、縦方向に90ドットくらいのフォントは欲しいところ。そうなると本当にネットに転がっているフォントなんて皆無に近いです。

U8gのフォントを利用する

ならば、種類もサイズも豊富なU8gのフォントを使ってみようかということになります。
フォントは一応ここに公開されています。
github.com
フォントの見本はこちら
github.com

ところが、ソースを見てもどの部分がビットマップなのかよくわからない。フォーマットを書いたヘルプがあるかと思って探しても見つからず。
ようやく見つけたのがこのサイト。
hackworlds.com
このサイトでは、ビットマップフォントをFonyというWindowsアプリで作成して、BDFというフォーマットで保存し、BDFからU8g用のフォーマットに変換する方法が書いてあります。
BDFフォーマットは、例えばこちら
software.fujitsu.com
を読めばだいたいのことは判ります。
そこで、BDFフォーマットとU8gのフォーマットを見比べて、U8gのファイルを解読してやれば巨大なビットマップフォントが手に入るはず。
そこで、Fonyでとりあえず、ちっちゃい0と1を作ってみました。
BDFファイルはこちら

STARTFONT 2.1
COMMENT Exported by Fony v1.4.7
FONT test
SIZE 12 96 96
FONTBOUNDINGBOX 9 11 0 -3
STARTPROPERTIES 6
COPYRIGHT "Created with Fony 1.4.7"
RESOLUTION_X 96
RESOLUTION_Y 96
FONT_ASCENT 9
FONT_DESCENT 3
DEFAULT_CHAR 0
ENDPROPERTIES
CHARS 2
STARTCHAR 048
ENCODING 48
SWIDTH 576 0
DWIDTH 8 0
BBX 6 8 1 0
BITMAP
FC
84
84
84
84
84
84
FC
ENDCHAR
STARTCHAR 049
ENCODING 49
SWIDTH 576 0
DWIDTH 8 0
BBX 2 8 2 0
BITMAP
40
C0
40
40
40
40
40
40
ENDCHAR
ENDFONT

文字のサイズが書いてあるところは上から5行目、FONTBOUNDINGBOXのところで、9×11となっています。後の2つの意味はよくわかりません。
そして、BITMAPの1つ上の行にもBBXというのがあって、上記の富士通のサイトではこれは、FONTBOUNDINGBOXと同じ数値を入れることになっていますが、このファイルでは異なっています。上のキャラクターは数字の0ですが、BBXは6×8となっています。また後ろに謎の数字が2つありますがとりあえず気にしないことにして、下のキャラクターは数字の1ですが、BBXは2×8となっています。察するに上から下までオールゼロの列は無視して何らかのデータがある列をカウントしているのではないかと思われます。なので、表示の細い1の方が2×8と横が小さい数字になっているのかと。
さてこれをU8g用に変換したファイルがこちらです。

/*
  Fontname: test
  Copyright: Created with Fony 1.4.7
  Capital A Height: 0, '1' Height: 8
  Calculated Max Values w= 6 h= 8 x= 2 y= 0 dx= 8 dy= 0 ascent= 8 len= 8
  Font Bounding box     w= 9 h=11 x= 0 y=-3
  Calculated Min Values           x= 0 y= 0 dx= 0 dy= 0
  Pure Font   ascent = 8 descent= 0
  X Font      ascent = 8 descent= 0
  Max Font    ascent = 8 descent= 0
*/
#include "u8g.h"
const u8g_fntpgm_uint8_t test[45] U8G_SECTION(".progmem.test") = {
  0,9,11,0,253,8,0,0,0,0,48,49,0,8,0,8,
  0,6,8,8,8,1,0,252,132,132,132,132,132,132,252,2,
  8,8,8,2,0,64,192,64,64,64,64,64,64};

include "u8g.h"以降が肝心なところですが、数字の羅列のところを見ると、初めの2つ目と3つ目がそれぞれ9と11になっています。これは、FONTBOUNDINGBOXの数値と同じです。そして、11番目と12番目が48,49となっています。これは0と1のアスキーコードです。そのあと6つ挟んで8,8となっています。これがBBXに相当するものかと思います。オールゼロを無視せずカウントすると8x8になりますから。そして、また3つ挟んで、252,132,.....252と続きます。ここが数字の0のビットマップの部分のように思います。そしてまた、2を挟んで8,8ときて、3つ挟んで64,192,64....64と続くのが数字の1のビットマップのようです。
なんとなくわかってきたんで、Fonyをつかっていろいろなフォントを作っているうちにもっといい方法を発見しました。

Fonyを使えばもっと楽ちん

じつは、FonyというソフトはWindowsにインストールされているフォントを取り込むことができることがわかりました。しかも任意のサイズでです。したがって、U8gの中から適当なサイズのフォントを探して、そしてファイルの内容を解析してといった面倒なことをせずに、FonyでBDFファイルを作ってやれば、そこに書かれているビットマップがそのまま使えるということです。
やり方ですが、Fonyを立ち上げて、File->Import->Installed TrueType Font を選択します。

そこから好きなフォント選んで、スタイルとサイズを選びます。サイズはデフォルトでは72ポイントまでしか選べませんが、数字を手で打ち込めばもっと大きなサイズも入力可能です。
色々なフォントがあって迷うところですが、Cambriaというフォントを選んでみました。取り込むときにFirst charとLast charの欄があってここにアスキーコードを打ち込んでほしいキャラクタを選択します。今回は数字と:だけあればいいので48と58を入力しました。(下のスクショは数字を入力する前なので、0と255になっています)

電子ペーパー用に加工する

今回試しているWaveShareの電子ペーパーでは、前回も書いたように、基本は縦長が正しい向きのようで、縦長にして、左上がディスプレイの原点(x=0、y=0)になっています。縦長に置いたときに横方向がx座標、縦方向がy座標です。
したがって、横長にして通常のビットマップフォントを表示させると横に90°傾いた表示になります。また、通常ビットマップフォントは1が表示で0が非表示ということになっていますが、電子ペーパーのメモリに書き込むデータは1が白、0が黒と通常とは逆になっています。したがって、横に90°回転させて、しかも0と1を反転したデータを用意する必要があります。
これらを全部Fonyでできれば言うことはなかったのですが、残念ながら回転する機能だけがありません。
仕方ないので、回転だけは自分でプログラムを書いて対応することにします。
また、データの幅と高さですが、BDFフォーマットでは、自動的に横幅を8bit(バイト)単位に調整してしまい、余った部分には勝手に0を埋めるというルールがあります。つまり、幅6bitのデータを作っても残りの2bitを0で埋めたデータを作ります。電子ペーパーでは、0は黒く表示されてしまいますので、幅が8の倍数でないフォントを作ってしまうと、縦に黒い筋が入ってしまうことになります。
幅は、Fony上で調整できるので、8の倍数になるように調整してやります。例えば、36ポイントのフォントサイズで読み込んだ場合は、横幅が22bitになるので、右上の黒三角をクリックして24に幅を広げます。するとフォント全体が中心からずれてしまうので今度は青の矢印を使って真ん中に調整します。
フォント全体を反転させるのはEdit->Invertを選択します。
そして、最後に上下を反転させます。Edit->Mirror->Mirror Vert. を選択します。

上下を反転させたのは、あとでプログラム上で、ビットマップを2次元配列に展開して、xとyを入れ替えれば丁度90°回転したフォントが出来上がるからです。
これで準備が整いました。File->Export->BDF fontを選べばBDFフォーマットのファイルが出来上がります。

Pythonで加工する

FonyでBDFファイルを出力したら今度はPythonを使ってフォントを回転させます。実は、フォントのビットマップを回転させるプログラムを書くのは結構厄介なのですが、上にも書いたようにあらかじめ上下反転しておけば、xとyを入れ替えるだけで90°回転してくれます。
まずは、BDFのファイルを開いて、お目当てのフォントのビットマップの部分を見つけます。
始めのうちは延々とビットマップ以外の部分が続きますが、その下にビットマップがずらっと並んでいます。今回作ったフォントで数字の0のところはこのようになっています。

BBX 24 43 0 -8
BITMAP
FFFFFF
FFFFFF
FFFFFF
FFFFFF
FFFFFF
FFFFFF
FFFFFF
FFFFFF
FFFFFF
FFFFFF
FF83FF
FE00FF
FE387F
FC7C7F
F8FE3F
F8FE3F
F9FE3F
F1FF1F
F1FF1F
F1FF1F
F1FF1F
F1FF1F
F1FF1F
F1FF1F
F1FF1F
F1FF1F
F1FF1F
F1FF1F
F8FF3F
F8FE3F
F8FE3F
FC7C7F
FC387F
FE00FF
FF83FF
FFFFFF
FFFFFF
FFFFFF
FFFFFF
FFFFFF
FFFFFF
FFFFFF
FFFFFF
ENDCHAR

BBXが24と43ですから24×43のサイズということになります。ここで気を付けないといけないのは、フォントの高さも8の倍数にする必要があるということです。なぜなら、このフォントは90°回転して表示するので、フォントの縦方向が電子ペーパーのx座標になってしまうため、電子ペーパーの制約上x軸方向は8bit単位でしか指定できないため8の倍数にしておく必要があります。この場合、8の倍数で一番近いのは40ですが、見てわかるように上下にFの羅列がたくさんあって、実は高さは32ドットでも数字の部分は十分に収まってしまいます。ですので、縦を32行分にして、適当に切り取って、メモ帳に貼り付け、適当な名前を付けて保存します。それをPythonを使って加工して回転してやります。
Pythonスクリプトです。

import numpy as np
import tkinter as tk

byte = 3
bit = byte * 8

data = np.loadtxt("d:/font/cambria_36_9.txt",  dtype = "unicode")
print(data)

font = []

for i in range(len(data)):
    font.append(data[i][0:2])
    font.append(data[i][2:4])
    font.append(data[i][4:6])

print(font)
n = np.array(font)
n1y = int(len(font)/byte)
n1 = np.zeros((n1y,byte),dtype='int')
n2 = np.zeros((n1y,bit),dtype='int')
n1 = n.reshape(n1y,byte)

for i in range(n1y):
    for j in range(byte):
        for k in range(8):
            bi = int(n1[i,j],16)
            bii = ((bi >> (7-k))&1)
            n2[i,j*8+k] = bii

print(n2)


fon=[]

for j in range(bit):
    for i in range((n1y//8)):
        by = 0
        for k in range(8):
            x = i*8+k
            by = by | (n2[x,j]<<(7-k))
        fon.append(by)

print (fon)


root=tk.Tk()
root.geometry('400x300')
canvas = tk.Canvas(root, width=400, height=300)
canvas.place(x=0, y=0)

for i in range(24):
    for j in range(32):
        if (n2[j,i]==1):
            canvas.create_rectangle(i*8, j*8, i*8+8, j*8+8, outline="black", fill="blue")

root.mainloop()

やっていることは極めてシンプルなので説明しなくてもわかるかと思いますが、初めの変数byteはバイト単位であらわしたフォントの横幅を示しています。今回は24ドットの横幅にしたのでbyteは3です。bitはその8倍でビット幅を示しています。ビットマップデータは、numpyのnp.loadtxtで変数dataに読み込んでいます。凝ったことをせずにただ順番に読み込むだけならこの方法がシンプルでお勧めです。
dataに読み込まれたデータは、1行に3バイト分が書かれているので、スライスを使って、これを1バイトずつに分離します。そして、fontという配列に順番に入れていきます。fontはリスト型の配列ですので、扱いやすいndarray に変換して、nという配列に入れます(あまり意味ないかもしれませんが、メモリーを無駄に使っている)。そして、n1、n2という二次元配列を2つ作ります。一つは、バイト単位のデータを格納するため、もう一つはビット単位のデータを格納するためです。したがってn2の配列には0か1しか入りません。マイコンでこんなプログラムを組んだら、なんてメモリの無駄遣いと怒られそうですが、潤沢にメモリーのあるPCですから、気にせず使っていきます。
その次のループがビット単位にばらすところです。配列n1を順番に読んでいき、ビットシフト機能で1ビットずつ読んでいき、n2に格納しています。
そして、その次のループが今度はxとyを入れ替えて、ビットデータをバイト単位のデータに直しています。配列はまたリスト型ですが、fonという配列を作ってそこに順番に入れていきます。
ここでは、結構原始的なんですが、画面に表示されたfonの中身をコピペしてビットマップデータとしています。また、読み込みも数字1つずつという原始的な方法を使っています。プログラムでBDFファイルを読み込んで内容を解析して自動的にxy座標を入れ替えたビットマップデータのファイルを出力するようにしてもよかったのですが、処理するのが数字だけでそんなに数が多くないので、テストを兼ねて作ったプログラムで済ませてしまっています。もっといろんなフォントを使うようになったらまた考えたいと思います。
最後のTkinterを使った描画のところはおまけみたいなもので、一応ビットマップデータを確認のため表示しています。xyを入れ替える前なので、Fonyの画面で見えていたものと同じく、白黒反転で上下にひっくり返った表示になります。
今回は結局電子ペーパーを使っていませんが、次回、この作ったフォントを電子ペーパーに表示してみます。