我使用的是 3.5:TFT LCD 显示器,带有 Arduino Uno 和制造商提供的库 KeDei TFT 库。该库带有一个位图字体表,对于 Arduino Uno 的少量内存来说它是巨大的,所以我一直在寻找替代品。
我遇到的是似乎没有标准的表示形式,我发现一些位图字体表工作正常,而另一些则显示为奇怪的涂鸦和标记,或者它们显示颠倒,或者它们显示时字母翻转。在编写了一个简单的应用程序来显示一些字符后,我终于意识到不同的位图使用不同的字符方向。
我的问题
位图字体的位数据的规则或标准或预期表示是什么?为什么位图字体似乎有几种不同的文本字符方向?
关于问题的想法
这些是否是由于不同的目标设备(例如 Windows 显示驱动程序或 Linux 显示驱动程序)与裸机 Arduino TFT LCD 显示驱动程序?
用于将特定位图字体表示确定为一系列无符号字符值的标准是什么?通过设置像素颜色在显示表面上绘图时,不同类型的光栅设备(例如 TFT LCD 显示器及其控制器)是否具有不同的位序列?
还有哪些其他可能的位图字体表示需要我的库版本当前不提供的转换?
除了我用来确定需要什么转换的方法之外,还有其他方法吗?我目前将位图字体表插入测试程序并打印出一组字符以查看其外观,然后通过使用 Arduino 和 TFT LCD 屏幕进行测试来微调转换。
到目前为止我的经验
KeDei TFT 库带有一个位图字体表,定义为
const unsigned char font_table_16_col[96][16] = {
{ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },/*" ",0*/
{ 0x00,0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x38,0x38,0x00,0x00 },/*"!",1*/
{ 0x00,0xD8,0xFC,0x6C,0x36,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },/*""",2*/
{ 0x00,0x00,0x00,0x6C,0x6C,0x6C,0xFF,0x36,0x36,0x36,0xFF,0x36,0x36,0x36,0x00,0x00 },/*"#",3*/
{ 0x00,0x00,0x18,0x3C,0x7E,0x7E,0x1E,0x1C,0x38,0x78,0x78,0x7E,0x7E,0x3C,0x18,0x18 },/*"$",4*/
{ 0x00,0x00,0x00,0x66,0x6F,0x3F,0x3F,0x3F,0x7E,0xF8,0xFC,0xFC,0xFC,0x66,0x00,0x00 },/*"%",5*/
...
我并不完全熟悉位图字体的标准描述,但我认为这是一种 8x16 位图字体,其中每个字符的宽度为 8 像素,高度为 16 像素,或者是 8x16 位图字体。
由于这张表的大小和 Arduino Uno 上的少量内存,我开始寻找其他清晰易读的位图字体,同时占用更少的内存。请参阅减少用于 Arduino 的 3.5" TFT 显示器的 KeDei TFT 库所需的内存
我希望找到的是围绕 6x6 位图字体的东西,这样位图字体表的定义就会改变,const unsigned char font_table_16_col[96][16] = {
从而const unsigned char font_table_16_col[96][6] = {
释放大量内存。通过删除小写字母来减少表格的实验表明,这也有帮助。
寻找替代的位图字体比我想象的要困难,设想某人在 GitHub 存储库中的某个地方拥有位图字体的母体,通过一两次搜索很容易找到。
我遇到的是,虽然我发现了几个不同的位图字体示例,但似乎并非都与我特定的 3.5" TFT LCD 显示器兼容。
例如,这里是四种不同位图字体的表示,显示了两个字符的位图位,感叹号 (!) 和双引号 (")。5x8 似乎顺时针旋转了 90 度。8x8 和16x8 似乎方向正确,而 13x8 似乎颠倒了。
生成上述位图表示
上图中的位图字体表示,显示了文本字符方向的差异,是由一个简单的 Windows GUI 生成的,并用一个短划线 (-) 表示位值 0 和一个星号 (*) 表示位值 1 . 这是 Microsoft Windows GUI 应用程序的输出,WM_PAINT
其绘制显示图像的消息处理程序如下:
int paintFontDisplay(HWND hWnd)
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
SetTextAlign(hdc, TA_CENTER);
RECT rect;
GetClientRect(hWnd, &rect);
//Logical units are device dependent pixels, so this will create a handle to a logical font that is 48 pixels in height.
//The width, when set to 0, will cause the font mapper to choose the closest matching value.
//The font face name will be Impact.
HFONT hFont = CreateFont(24, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_OUTLINE_PRECIS,
CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, FIXED_PITCH, TEXT("Courier"));
SelectObject(hdc, hFont);
// TODO: Add any drawing code that uses hdc here...
int iFirst = 0;
int iLast = 10;
POINT outPoint;
outPoint.x = rect.left + 80;
outPoint.y = rect.top + 20;
for (int i = iFirst; i < iLast; i++) {
for (int j = 0; j < 5; j++) {
std::wstring charRep;
for (unsigned char k = 0x80; k; k >>= 1) {
if (font_table_5_col[i][j] & k) {
charRep += '*';
}
else {
charRep += '-';
}
}
TextOut(hdc, outPoint.x, outPoint.y, charRep.c_str(), charRep.length());
outPoint.y += 20;
}
outPoint.y += 20 + 20 * 11;
}
outPoint.x = outPoint.x + 200;
outPoint.y = rect.top + 20;
for (int i = iFirst; i < iLast; i++) {
for (int j = 0; j < 8; j++) {
std::wstring charRep;
for (unsigned char k = 0x80; k; k >>= 1) {
if (font_table_8_col[i][j] & k) {
charRep += '*';
}
else {
charRep += '-';
}
}
TextOut(hdc, outPoint.x, outPoint.y, charRep.c_str(), charRep.length());
outPoint.y += 20;
}
outPoint.y += 20 + 20 * 8;
}
outPoint.x = outPoint.x + 200;
outPoint.y = rect.top + 20;
for (int i = iFirst; i < iLast; i++) {
for (int j = 0; j < 13; j++) {
std::wstring charRep;
for (unsigned char k = 0x80; k; k >>= 1) {
if (font_table_13_col[i][j] & k) {
charRep += '*';
}
else {
charRep += '-';
}
}
TextOut(hdc, outPoint.x, outPoint.y, charRep.c_str(), charRep.length());
outPoint.y += 20;
}
outPoint.y += 20 + 20 * 3;
}
outPoint.x = outPoint.x + 200;
outPoint.y = rect.top + 20;
for (int i = iFirst; i < iLast; i++) {
for (int j = 0; j < 16; j++) {
std::wstring charRep;
for (unsigned char k = 0x80; k; k >>= 1) {
if (font_table_16_col[i][j] & k) {
charRep += '*';
}
else {
charRep += '-';
}
}
TextOut(hdc, outPoint.x, outPoint.y, charRep.c_str(), charRep.length());
outPoint.y += 20;
}
outPoint.y += 20;
}
EndPaint(hWnd, &ps);
return 0;
}
位图字体表的前几行如下:
// following table from URL https://forum.arduino.cc/t/font-generation-for-bitmaps/161582/11
const unsigned char font_table_5_col[96][5] = {
{ 0x00, 0x00, 0x00, 0x00, 0x00 } // 20
,{ 0x00, 0x00, 0x5f, 0x00, 0x00 } // 21 !
,{ 0x00, 0x07, 0x00, 0x07, 0x00 } // 22 "
,{ 0x14, 0x7f, 0x14, 0x7f, 0x14 } // 23 #
,{ 0x24, 0x2a, 0x7f, 0x2a, 0x12 } // 24 $
// See https://github.com/dhepper/font8x8
const unsigned char font_table_8_col[96][8] = {
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0020 (space)
{ 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00 }, // U+0021 (!)
{ 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0022 (")
{ 0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00 }, // U+0023 (#)
{ 0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00 }, // U+0024 ($)
const unsigned char font_table_13_col[96][13] = {
// from URL https://courses.cs.washington.edu/courses/cse457/98a/tech/OpenGL/font.c
// GLubyte rasters[][13] = {
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },/*" ",0*/
{ 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18 },/*"!",1*/
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x36, 0x36, 0x36 },/*""",2*/
{ 0x00, 0x00, 0x00, 0x66, 0x66, 0xff, 0x66, 0x66, 0xff, 0x66, 0x66, 0x00, 0x00 },/*"#",3*/
{ 0x00, 0x00, 0x18, 0x7e, 0xff, 0x1b, 0x1f, 0x7e, 0xf8, 0xd8, 0xff, 0x7e, 0x18 },/*"$",4*/
转换字体位图以正确显示
我已经修改了使用位图字体显示文本的代码,以便对于特定的位图,字符绘制逻辑将在位图字体表示为一系列十六进制数字以及如何使用这一系列数字之间执行几种不同类型的转换确定打开哪些像素以及关闭哪些像素。
绘制单行字符的代码如下。该函数的概要是向 LCD 控制器提供一个矩形,指定要修改的显示区域,然后进行一系列两个 8 位写入,以设置该区域中每个像素的两字节 RGB565 颜色值。
static bool TFTLCD::draw_glyph(unsigned short x0, unsigned short y0, TftColor fg_color, TftColor bg_color, unsigned char bitMap, unsigned char bmWidth, unsigned char flags)
{
// we will fill a single row of 8 pixels by iterating over
// a bitmap font map of which pixels to set to the foreground
// color and which pixels to set to the background color for this
// part of the character to display.
// first determine whether we are scaling the default width by a multiplier
// of 2 or 3 times the default size. this allows us to have different sizes
// of text using the same bitmap font.
if (flags & 0x01)
set_area(x0, y0, x0 + bmWidth * 2 - 1, y0); // scale the default width to double wide
else if (flags & 0x02)
set_area(x0, y0, x0 + bmWidth * 3 - 1, y0); // scale the default width to tripple wide
else
set_area(x0, y0, x0 + bmWidth - 1, y0); // default width and size with no scaling
if (flags & 0x20) { // Font::font_flags & FontTable::Flags_InvertBitOrder
// inverting the order of painting the bits. means the bitmap of the
// font would display the text with each character flipped if we did not do this.
for (unsigned char char_n = 0x80; char_n; char_n >>= 1)
{
if (bitMap & char_n)
{
w_data(fg_color >> 8);
w_data(fg_color);
}
else {
w_data(bg_color >> 8);
w_data(bg_color);
}
if (flags & 0x03) { // double wide or triple wide
if (bitMap & char_n)
{
w_data(fg_color >> 8);
w_data(fg_color);
}
else {
w_data(bg_color >> 8);
w_data(bg_color);
}
if (flags & 0x02) { // triple wide
if (bitMap & char_n)
{
w_data(fg_color >> 8);
w_data(fg_color);
}
else {
w_data(bg_color >> 8);
w_data(bg_color);
}
}
}
}
}
else {
for (unsigned char char_n = 1; char_n; char_n <<= 1)
{
if (bitMap & char_n)
{
w_data(fg_color >> 8);
w_data(fg_color);
}
else {
w_data(bg_color >> 8);
w_data(bg_color);
}
if (flags & 0x03) { // double wide or triple wide
if (bitMap & char_n)
{
w_data(fg_color >> 8);
w_data(fg_color);
}
else {
w_data(bg_color >> 8);
w_data(bg_color);
}
if (flags & 0x02) { // triple wide
if (bitMap & char_n)
{
w_data(fg_color >> 8);
w_data(fg_color);
}
else {
w_data(bg_color >> 8);
w_data(bg_color);
}
}
}
}
}
return 1;
使用上述函数绘制完整字符的源代码如下。此代码使用该drawGlyph()
函数从上到下绘制一系列文本字符切片。何时完成位图转换取决于位图表示。
unsigned char glyphFlags = ((Font::font_flags & FontTable::Flags_DoubleWide) ? 1 : 0) | ((Font::font_flags & FontTable::Flags_TripleWide) ? 2 : 0);
if (Font::font_flags & FontTable::Flags_InvertBitOrder) {
glyphFlags |= 0x20;
for (signed char char_m = Font::font_table.nCols - 1; char_m >= 0; char_m--)
{
TFTLCD::draw_glyph(Font::now_x, Font::now_y, Font::font_color, Font::txt_backcolor, Font::font_table.table[char_i_x + char_m], Font::font_table.nCols, glyphFlags);
// shift down to the next row of pixels for the character
Font::now_y++;
if (font_flags & (FontTable::Flags_DoubleHigh | FontTable::Flags_TripleHigh)) {
TFTLCD::draw_glyph(Font::now_x, Font::now_y, Font::font_color, Font::txt_backcolor, Font::font_table.table[char_i_x + char_m], Font::font_table.nCols, glyphFlags);
// shift down to the next row of pixels for the character
Font::now_y++;
}
if (font_flags & FontTable::Flags_TripleHigh) {
TFTLCD::draw_glyph(Font::now_x, Font::now_y, Font::font_color, Font::txt_backcolor, Font::font_table.table[char_i_x + char_m], Font::font_table.nCols, glyphFlags);
// shift down to the next row of pixels for the character
Font::now_y++;
}
}
}
else if (Font::font_flags & FontTable::Flags_RotateBits) {
for (unsigned char char_m = 0; char_m < 8; char_m++)
{
unsigned char rotatedMap = 0;
for (unsigned char char_x = 0; char_x < Font::font_table.nCols; char_x++) {
rotatedMap |= ((Font::font_table.table[char_i_x + char_x] & (1 << char_m)) ? 1 : 0) << char_x;
}
TFTLCD::draw_glyph(Font::now_x, Font::now_y, Font::font_color, Font::txt_backcolor, rotatedMap, 8, glyphFlags);
// shift down to the next row of pixels for the character
Font::now_y++;
if (font_flags & (FontTable::Flags_DoubleHigh | FontTable::Flags_TripleHigh)) {
TFTLCD::draw_glyph(Font::now_x, Font::now_y, Font::font_color, Font::txt_backcolor, rotatedMap, 8, glyphFlags);
// shift down to the next row of pixels for the character
Font::now_y++;
}
if (font_flags & FontTable::Flags_TripleHigh) {
TFTLCD::draw_glyph(Font::now_x, Font::now_y, Font::font_color, Font::txt_backcolor, rotatedMap, 8, glyphFlags);
// shift down to the next row of pixels for the character
Font::now_y++;
}
}
}
else {
for (unsigned char char_m = 0; char_m < Font::font_table.nCols; char_m++)
{
TFTLCD::draw_glyph(Font::now_x, Font::now_y, Font::font_color, Font::txt_backcolor, Font::font_table.table[char_i_x + char_m], Font::font_table.nCols, glyphFlags);
// shift down to the next row of pixels for the character
Font::now_y++;
if (font_flags & (FontTable::Flags_DoubleHigh | FontTable::Flags_TripleHigh)) {
TFTLCD::draw_glyph(Font::now_x, Font::now_y, Font::font_color, Font::txt_backcolor, Font::font_table.table[char_i_x + char_m], Font::font_table.nCols, glyphFlags);
// shift down to the next row of pixels for the character
Font::now_y++;
}
if (font_flags & FontTable::Flags_TripleHigh) {
TFTLCD::draw_glyph(Font::now_x, Font::now_y, Font::font_color, Font::txt_backcolor, Font::font_table.table[char_i_x + char_m], Font::font_table.nCols, glyphFlags);
// shift down to the next row of pixels for the character
Font::now_y++;
}
}
}
光栅或位图字体规范
有许多字体规范,包括光栅化位图类型字体。这些规范不一定描述在诸如 KeDei TFT 库之类的应用程序中使用的字形位图,而是提供与设备无关的位图字体格式描述。
字形位图分布格式
“BITMAP”开始当前字形的位图。这条线后面必须跟 Y 轴上每个像素的一条线。在这个例子中,字形是 16 像素高,所以后面有 16 行。每行包含一行中像素的十六进制表示。“1”位表示渲染像素。每行四舍五入到 8 位(一个字节)边界,右侧用零填充。在这个例子中,字形正好是 8 个像素宽,因此每行正好占据 8 位(一个字节),因此没有填充。一行栅格数据的最高有效位代表最左边的像素。
Oracle 在 Solarix X Window System Developer's Guide, Chapter 4 Font Support at https://docs.oracle.com/cd/E19253-01/816-0279/6m6pd1cvk/index.html有一个表格列出了几种不同的位图字体格式,并且这就是说:
如表 4-4 所示,许多位图字体文件格式是依赖于体系结构的二进制文件。它们不能在不同架构的机器之间共享(例如,在 SPARC 和 IA 之间)。
- 位图分布格式,.bdf 文件,不是二进制文件,不是特定于架构的
- 可移植的编译格式,.pcf 文件,二进制,不是特定于架构的
- Little Endian 预建格式,二进制,架构特定
- Big Endian 预建格式,二进制,架构特定
PSF(PC 屏幕字体),一种二进制规范,描述于 URL https://www.win.tue.nl/~aeb/linux/kbd/font-formats-1.html
PSF 代表 PC 屏幕字体。没有 Unicode 映射的 psf1 格式是 H. Peter Anvin 在 1989 年左右为他的 DOS 屏幕字体编辑器 FONTEDIT.EXE 设计的。1994 年 10 月,他添加了 Unicode 映射和程序 psfaddtable、psfgettable、psfstriptable 来操作它 - 参见 kbd-0.90。为了处理藏文,Andries Brouwer 在 1999 年 9 月增加了对 Unicode 值序列和 psf2 格式的支持 - 请参阅 kbd-1.00。
来自“早期 Microsoft 知识库文章存档”的 Microsoft Q65123 https://jeffpar.github.io/kbarchive/kb/065/Q65123/
Microsoft Windows 字体文件的格式是为光栅和矢量字体定义的。在某些 GDI 支持模块中,智能文本生成器可以使用这些格式。尤其是矢量格式,GDI 本身比支持模块更频繁地使用这些格式。
Metagraphics .fnt 字体文件规范https://www.metagraphics.com/metawindow/fonts/fnt-specs.htm
Microchip 图形库,Microchip 图形库中的 AN1182 字体 (PDF)
也可以看看
这个文件格式网站有几种不同字体规范的描述。https://docs.fileformat.com/font/fnt/