位图或光栅化字体位图和 3.5" TFT LCD 上的文本显示差异 对问题的思考生成上述位图表示光栅或位图字体规范

问题描述

我使用的是 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 },/*" ",0*/
    { 0x00,0x18,0x38,/*"!",1*/
    { 0x00,0xD8,0xFC,0x6C,0x36,/*""",2*/
    { 0x00,0xFF,/*"#",3*/
    { 0x00,0x3C,0x7E,0x1E,0x1C,0x78,0x18 },/*"$",4*/
    { 0x00,0x66,0x6F,0x3F,0xF8,/*"%",5*/
...

我不完全熟悉位图字体的标​​准描述,但我认为这是一种 8x16 位图字体,其中每个字符的宽度为 8 像素,高度为 16 像素或 8x16 位图字体。

由于这个表的大小和 Arduino Uno 上的少量内存,我开始寻找其他位图字体,这些字体既清晰又占用更少的内存。见reducing memory required for KeDei TFT library used with 3.5" TFT display with Arduino

我希望找到的是围绕 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 似乎颠倒了。

character representation of bitmap font samples showing differences

生成上述位图表示

上图中的位图字体表示,显示了文本字符方向的差异,是由一个简单的 Windows GUI 生成的,并用代表零位值的破折号 (-) 和代表位值的星号 (*) 显示值为 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,FW_DONTCARE,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,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,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,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 } // 20,{ 0x00,0x5f,0x00 } // 21 !,0x07,0x00 } // 22 ",{ 0x14,0x7f,0x14,0x14 } // 23 #,{ 0x24,0x2a,0x12 } // 24 $

// See https://github.com/dhepper/font8x8
const unsigned char font_table_8_col[96][8] = {
    { 0x00,// U+0020 (space)
    { 0x18,// U+0021 (!)
    { 0x36,// U+0022 (")
    { 0x36,0x7F,// U+0023 (#)
    { 0x0C,0x3E,0x03,0x30,0x1F,0x0C,// 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,0x36 },0xff,0x7e,0x1b,0x1f,0xf8,0xd8,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,x0 + bmWidth * 3 - 1,y0); // scale the default width to tripple wide
    else
        set_area(x0,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,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,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,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,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 添加了对 Unicode 序列的支持 1999 年 9 月的值和 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 图形库,AN1182 Fonts in the Microchip Graphics Library (PDF)

另见

Where I can find .fon format specification?

这个文件格式网站描述了几种不同的字体规范。 https://docs.fileformat.com/font/fnt/

解决方法

光栅或位图字体以多种不同的方式表示,并且已经为 Linux 和 Windows 开发了位图字体文件标准。然而,编程语言源代码中位图字体的原始数据表示似乎取决于:

  • 目标计算机的内存架构,
  • 显示控制器的架构和通信路径,
  • character glyph 高度和宽度(以像素为单位)和
  • 用于位图存储的内存量以及采取了哪些措施使其尽可能小。

位图字体的简要概述

通用位图是一个数据块,其中单独的位用于指示打开或关闭的状态。位图的一种用途是存储图像数据。字符字形可以作为一组图像创建和存储,字符集中的每个字符一个,因此使用位图来编码和存储每个字符图像是很自然的。

位图字体是用于指示如何通过打开或关闭像素或在页面上打印或不打印点来显示或打印字符的位图。见Wikipedia Bitmap fonts

位图字体是一种将每个字形存储为像素数组的字体 (即位图)。它不常被称为光栅字体或 像素字体。位图字体只是光栅图像的集合 字形。对于字体的每个变体,都有一套完整的字形 图像,每组包含每个字符的图像。为了 例如,如果字体具有三种大小,并且粗体和 斜体,则必须有12组完整的图像。

位图字体使用简史

最早的用户界面终端如 teletype terminals 使用 dot matrix printer 机制在纸卷上打印。随着 Cathode Ray Tube terminals 位图字体的发展,随着发光点由扫描电子枪打开和关闭,可以很容易地转移到该技术中。

最早的位图字体具有固定的高度和宽度,位图作为一种印章或图案在输出介质、纸张或显像管上打印字符,具有固定的行高和固定的行宽,例如DEC VT-100 terminal 的 80 列和 24 行。

随着处理能力的提高,使用矢量字体可以使用更复杂的排版方法,用于提高显示文本质量并提供改进的缩放,同时减少描述字符字形所需的内存。

此外,虽然点阵或像素矩阵对于英语等语言效果很好,但位图字体对具有复杂字形形式的书面语言的效果不佳。

源代码中位图字体的表示

有多种位图字体文件格式,它们提供了一种在独立于设备的描述中表示位图字体的方法。有关示例,请参阅 Wikipedia topic - Glyph Bitmap Distribution Format

Adobe 的字形位图分布格式 (BDF) 是一种文件格式 用于存储位图字体。内容采用文本文件的形式 旨在成为人类和计算机可读的。 BDF 通常用于 Unix X 窗口环境。它已在很大程度上被 PCF 取代 更有效的字体格式,并通过可缩放字体 例如 OpenType 和 TrueType 字体。

其他位图标准(例如 XBM、Wikipedia topic - X BitMap 或 XPM、Wikipedia topic - X PixMap)是描述位图的源代码组件,但其中许多并非专门用于位图字体,而是用于其他图形图像,例如图标、光标等

由于位图字体是一种较旧的格式,很多时候位图字体被包装在另一种字体标准(如 TrueType)中,以便与现代操作系统(如 Linux 和 Windows)的标准字体子系统兼容。

然而,在裸机上运行或使用 RTOS 的嵌入式系统通常需要类似于 XBM 格式的原始位图字符图像数据。请参阅Encyclopedia of Graphics File Formats,其中包含以下示例:

以下是使用其 X10 存储的 16x16 位图的示例 和 X11 变体。请注意,每个数组包含完全相同的 数据,但使用不同的数据字类型存储:

/* XBM X10 format */
#define xlogo16_width 16
#define xlogo16_height 16

static unsigned short xlogo16_bits[] = {
  0x0f80,0x1e80,0x3c40,0x7820,0x7810,0xf008,0xe009,0xc005,0xc002,0x4007,0x200f,0x201e,0x101e,0x083c,0x0478,0x02f0};

/* XBM X11 format */
#define xlogo16_width 16
#define xlogo16_height 16

static unsigned char xlogo16_bits[] = {
    0x0f,0x80,0x1e,0x3c,0x40,0x78,0x20,0x10,0xf0,0x08,0xe0,0x09,0xc0,0x05,0x02,0x07,0x0f,0x04,0xf0};

位图字体中每条数据的位的遍历顺序对于实现预期结果很重要。

因为每个像素只有一位大小,数组中的每个字节 包含八个像素的信息,第一个像素在 位图(在位置 0,0)由第一个字节的高位表示 在数组中。如果图像宽度不是八的倍数,额外的 每行最后一个字节中的位未被使用并被忽略。

虽然这个描述看起来足够好,但“第一个字节的高位”的定义因机器架构而异,Big-endian versus Little-endian。下图使用上述 X 标志的 XBM 描述绘制,左侧显示了在 Intel i7-7900 CPU 上从最高有效位到最低有效位遍历每个位图数据字节的位,右侧则相反。>

Displayed XBM 16x16 image Big-endian versus Little-endian

位图字体的注意事项

位图字体具有以像素或点为单位的单元格大小或字符高度和宽度。一行文本是在显示屏上逐个像素标记或绘制的一系列单元格。

由于位图字体的位图不是独立于设备的,是一系列数字,因此描述位图字体字符图像的原始数据以及该数据如何存储在内存中并由 CPU 访问取决于设备。还可以转换位图数据以更有效地使用机器资源。

位图字体的位图可以以这样一种方式存储,即它们可以节省使用的内存,同时需要额外的处理才能将像素正确放置在绘图表面上。因此,为了充分使用 unsigned char 的 8 位,可能会先存储 5 像素宽 x 8 像素高的字体,而不是先存储高度。

除了旋转位图以更有效地使用字节中的位外,还可以使用压缩算法来减少位图字体表所需的内存量。有关方法的讨论,请参阅 Lightweight (de)compression algorithm for embedded use

机器架构可以以不同的方式物理访问代表像素的各个位。位图字体的原始数据布局供 Little-endian 机器处理将与 Big-endian 机器布局原始数据的方式不同。请参阅 IBM Writing endian-independent code in C 的这篇文章。

拥有一个工具来可视化位图字体并允许对位图字体数据进行各种转换以探索显示位图字符可能需要进行哪些更改是有帮助的。例如,这是我用来试验位图字体的 Windows GUI 应用程序,https://github.com/RichardChambers/utilities_tools/tree/main/fontshow

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...