嵌入式应用实例→电子产品量产工具→字符显示系统(可支持多种字符显示,但这里只用了FreeType一种)代码分析和上板测试记录

头文件include\font_manager.h中定义的重要结构体

结构体FontBitMap

typedef struct FontBitMap {
	Region tRegion;
	int iCurOriginX;
	int iCurOriginY;
	int iNextOriginX;
	int iNextOriginY;
	unsigned char *pucBuffer;
}FontBitMap, *PFontBitMap;

结构体Region tRegion表示这个字符的显示区域,结构体tRegion的定义如下:

typedef struct Region {
	int iLeftUpX;
	int iLeftUpY;
	int iWidth;
	int iHeigh;
}Region, *PRegion;

其中(iLeftUpX, iLeftUpY)表示这个区域的左上顶点的坐标, iWidth表示这个区域的宽度,iHeigh表示这个区域的高度。

用FreeType矢量字库来显示每一个字符,则还需当前字符的基点坐标(iCurOriginX, iCurOriginY),即上图中的origin点的坐标,下一个字符的基点坐标(iNextOriginX, iNextOriginY),具体如下图所示:
在这里插入图片描述

那我们怎么得一个字符的FontBitMap信息呢?可以定义名叫“FontOpr”的结构体来进行与字库相关的操作,通过调用它里边的成员函数GetFontBitMap,就可以得一个字符的FontBitMap信息,介绍如下。

结构体DispOpr

typedef struct FontOpr {
	char *name;
	int (*FontInit)(char *aFineName);
	int (*SetFontSize)(int iFontSize);
	int (*GetFontBitMap)(unsigned int dwCode, PFontBitMap ptFontBitMap);
	struct FontOpr *ptNext;
}FontOpr, *PFontOpr;

这个结构体叫字库操作结构体,通过它,可以得到字库的一系列操作方法。
成员函数FontInit 表示字库的初始化函数,其参数aFineName表示字库的名字。
成员函数SetFontSize用于设置字库的字体大小。
成员函数GetFontBitMap用于获得某个字符的位图信息。

文件font\freetype.c的分析

文件font\freetype.c的主要功能

文件font\freetype.c主要是用来实现对fretype字库的相关操作。

定义一个结构体FontOpr的全局实例

static FontOpr g_tFreetypeOpr = {
	.name          = "freetype",
	.FontInit      = FreeTypeFontInit,
	.SetFontSize   = FreeTypeSetFontSize,
	.GetFontBitMap = FreeTypeGetFontBitMap,
};

这没什么好说的,在这个C文件中,我们关键是要实现这个实例中需要的各成员函数。

freetype的初始化函数FreeTypeFontInit()

static int FreeTypeFontInit(char *aFineName)
{
    FT_Library    library;
    int error;

    error = FT_Init_FreeType( &library );                 /* initialize library */    
	if (error)
	{
		printf("FT_Init_FreeType err\n");
		return -1;
	}
	
    error = FT_New_Face(library, aFineName, 0, &g_tFace ); /* create face object */
	if (error)
	{
		printf("FT_New_Face err\n");
		return -1;
	}

    FT_Set_Pixel_Sizes(g_tFace, g_iDefaultFontSize, 0);

	return 0;
}

代码error = FT_New_Face(library, aFineName, 0, &g_tFace );

这段代码中调用了 FreeType 库的 FT_New_Face 函数,用于加载字体文件并创建一个新的字体对象。在嵌入式 Linux 开发中,使用 FreeType 通常与文字渲染有关,尤其是在需要显示不同字体样式或动态文本的场景下。

以下是对这段代码的详细分析:


函数原型

FT_EXPORT(FT_Error) FT_New_Face(
    FT_Library   library,
    const char*  filepathname,
    FT_Long      face_index,
    FT_Face*     aface
);

参数解析

  1. library

    • 类型:FT_Library
    • 描述:FreeType 库的实例,通常在初始化时通过 FT_Init_FreeType(&library) 创建。它是管理 FreeType 资源的上下文对象。
  2. filepathname (代码中 aFineName)

    • 类型:const char*
    • 描述:字体文件的路径,通常是 .ttf, .otf, .pfa, 或其他支持的字体格式文件。
    • 作用:指定加载的字体文件。
  3. face_index

    • 类型:FT_Long
    • 描述:指定字体文件中要加载的字体索引,通常为 0(表示加载字体文件的第一个字体)。
    • 作用:某些字体文件可能包含多个字体,这个参数用于指定要加载哪一个。
  4. aface (代码中 g_tFace)

    • 类型:FT_Face*
    • 描述:指向 FT_Face 的指针,用于接收新加载的字体对象。
    • 作用:FT_Face 包含了字体的元信息(如字符映射、轮廓信息、度量信息等)以及用于文本渲染的资源。

返回值

  • 返回类型:FT_Error
  • 描述:函数调用的状态码,返回值为 0 表示成功,否则表示失败并包含错误代码。

代码FT_Set_Pixel_Sizes(g_tFace, g_iDefaultFontSize, 0);

这段代码使用 FreeType 的 FT_Set_Pixel_Sizes 函数设置字体的像素大小,以便后续渲染文本。它与字体的显示尺寸和渲染效果直接相关。


函数原型

FT_EXPORT(FT_Error) FT_Set_Pixel_Sizes(
    FT_Face  face,
    FT_UInt  pixel_width,
    FT_UInt  pixel_height
);

参数解析

  1. face (代码中 g_tFace)

    • 类型:FT_Face
    • 描述:已经通过 FT_New_Face 加载的字体对象。
    • 作用:指定要设置尺寸的字体。
  2. pixel_width (代码中 g_iDefaultFontSize)

    • 类型:FT_UInt
    • 描述:设置字符的宽度,以像素为单位。这里咱们设置为自定义的 g_iDefaultFontSize,如果设为 0,表示按照 pixel_height 计算宽度,保持字符比例。
    • 作用:在等宽字体中使用较多,或者需要强制设置字体宽度时使用。
  3. pixel_height (代码中的 0)

    • 类型:FT_UInt
    • 描述:设置字符的高度,以像素为单位。如果设为 0,高度将根据 pixel_width 和字体比例计算。
    • 作用:指定字体的高度,常用于指定文字大小。

返回值

  • 返回类型:FT_Error
  • 描述:返回值为 0 表示成功,否则返回错误代码。

功能与作用

  1. 设置字体的渲染大小
    通过指定像素宽度和高度,调整字体在屏幕上的渲染尺寸。FreeType 会根据设置的大小计算字体的度量信息(例如字符间距、行高等)。

  2. 影响文字渲染效果
    如果 pixel_widthpixel_height 不匹配字体的比例,字符可能会变形。

  3. 嵌入式适配性
    嵌入式设备通常分辨率较低,通过设置合适的像素大小,可以优化文字显示效果。


freetype设置字体大小的函数

static int FreeTypeSetFontSize(int iFontSize)
{
    FT_Set_Pixel_Sizes(g_tFace, iFontSize, 0);
	return 0;
}

这个没什么好说的,而且函数FT_Set_Pixel_Sizes()已在初始化的时候用到过了,也在初始化函数中介绍过了。

获取某个字符的位图对象的函数FreeTypeGetFontBitMap()

static int FreeTypeGetFontBitMap(unsigned int dwCode, PFontBitMap ptFontBitMap)
{
	int error;
    FT_Vector pen;
    FT_GlyphSlot slot = g_tFace->glyph;

    pen.x = ptFontBitMap->iCurOriginX * 64; /* 单位: 1/64像素 */
    pen.y = ptFontBitMap->iCurOriginY * 64; /* 单位: 1/64像素 */

	/* 转换:transformation */
	FT_Set_Transform(g_tFace, 0, &pen);

	/* 加载位图: load glyph image into the slot (erase previous one) */
	error = FT_Load_Char(g_tFace, dwCode, FT_LOAD_RENDER);
	if (error)
	{
		printf("FT_Load_Char error\n");
		return -1;
	}

	ptFontBitMap->pucBuffer = slot->bitmap.buffer;

	ptFontBitMap->tRegion.iLeftUpX = slot->bitmap_left;
	ptFontBitMap->tRegion.iLeftUpY = ptFontBitMap->iCurOriginY*2 - slot->bitmap_top;
	ptFontBitMap->tRegion.iWidth   = slot->bitmap.width;
	ptFontBitMap->tRegion.iHeigh   = slot->bitmap.rows;
	ptFontBitMap->iNextOriginX = ptFontBitMap->iCurOriginX + slot->advance.x / 64;
	ptFontBitMap->iNextOriginY = ptFontBitMap->iCurOriginY;

	return 0;
}

函数 FreeTypeGetFontBitMap 是一个利用 FreeType 库获取指定字符的位图信息的函数。它通过设置字体对象的位置、加载字符的位图信息,然后计算字符在屏幕上的绘制区域和下一个字符的起始位置。


函数的流程解析

1. 参数含义
  • dwCode
    字符的编码(Unicode 或其他编码),用来指定要加载的字符。

  • ptFontBitMap
    指向 FontBitMap 的指针,存储获取到的位图信息,包括绘制区域、缓冲区和字符的起始与结束位置。


2. 关键步骤
(1) 定义和初始化变量
FT_Vector pen;
FT_GlyphSlot slot = g_tFace->glyph;
  • pen 用于设置文字的原点(光标位置)。
  • slot 指向当前字符的字形槽(FT_GlyphSlot),其中存储了字符的位图信息、度量信息等。
(2) 设置光标位置
pen.x = ptFontBitMap->iCurOriginX * 64;
pen.y = ptFontBitMap->iCurOriginY * 64;
FT_Set_Transform(g_tFace, 0, &pen);
(3) 加载字符位图
error = FT_Load_Char(g_tFace, dwCode, FT_LOAD_RENDER);
if (error) {
    printf("FT_Load_Char error\n");
    return -1;
}
  • FT_Load_Char 加载 dwCode 指定的字符到 slot 中,并生成其位图(渲染)。
  • 使用标志 FT_LOAD_RENDER 表示生成光栅化的位图(而非矢量轮廓)。
  • 如果加载失败(error != 0),函数返回 -1
(4) 获取位图缓冲区和绘制区域
ptFontBitMap->pucBuffer = slot->bitmap.buffer;

ptFontBitMap->tRegion.iLeftUpX = slot->bitmap_left;
ptFontBitMap->tRegion.iLeftUpY = ptFontBitMap->iCurOriginY * 2 - slot->bitmap_top;
ptFontBitMap->tRegion.iWidth   = slot->bitmap.width;
ptFontBitMap->tRegion.iHeigh   = slot->bitmap.rows;
  • pucBuffer
    存储字符的位图数据(灰度值或二值化)。从 slot->bitmap.buffer 获取。

  • 绘制区域 (tRegion)

    • iLeftUpX: 字符左上角的 X 坐标,基于当前光标位置(bitmap_left 表示左侧的偏移量)。
    • iLeftUpY: 字符左上角的 Y 坐标,基于光标的 Y 位置减去字符顶部的偏移(bitmap_top 表示顶部偏移量)。
    • iWidth: 字符位图的宽度。
    • iHeigh: 字符位图的高度。
(5) 计算下一个字符的起始位置
ptFontBitMap->iNextOriginX = ptFontBitMap->iCurOriginX + slot->advance.x / 64;
ptFontBitMap->iNextOriginY = ptFontBitMap->iCurOriginY;
  • iNextOriginX 计算下一个字符的 X 起始位置,基于当前字符的宽度加上字符间的间距(slot->advance.x 表示字符宽度及间距,单位为 1/64 像素)。
  • iNextOriginY 保持当前字符的 Y 起始位置不变。
(6) 返回结果
  • 如果成功加载并计算位图信息,返回 0,否则返回 -1

数据流示例

假设 dwCode'A' 的 Unicode 编码,当前光标位置为 (10, 20),加载后的 slot 中位图数据如下:

  • bitmap.buffer: 位图像素数据(例如,灰度值数组)。
  • bitmap_left: 2
  • bitmap_top: 15
  • bitmap.width: 12
  • bitmap.rows: 14
  • advance.x: 1024(表示 16 像素的字符宽度,1/64 像素单位)。

函数的输出:

  • pucBuffer: 指向位图像素数据。
  • tRegion.iLeftUpX: 2
  • tRegion.iLeftUpY: 20 * 2 - 15 = 25
  • tRegion.iWidth: 12
  • tRegion.iHeigh: 14
  • iNextOriginX: 10 + 16 = 26
  • iNextOriginY: 20

关键点总结

  1. 设置光标位置

    • 使用 FT_Set_Transform 确保字符在指定位置渲染。
  2. 加载字符位图

    • 通过 FT_Load_Char 渲染字符,并提取其位图缓冲区和度量信息。
  3. 计算绘制区域和下一个字符的起始位置

    • 根据 bitmap_left, bitmap_top 等信息确定字符的屏幕坐标。
    • 使用 advance.x 确定下一个字符的光标位置。
  4. 应用场景

    • 用于文字排版时,根据字符绘制信息逐一绘制字符串。
    • 适合嵌入式系统中字体渲染模块的实现。

如果需要进一步优化或扩展(如支持旋转、缩放、动态字体加载),可以添加更多的处理逻辑。

文件font\font_manager.c的分析

文件font\font_manager.c的功能

文件font\font_manager.c用于实现多种字符库的选择和管理功能,它的功能是下图中红框框着的部分。
在这里插入图片描述

函数RegisterFont()的分析

void RegisterFont(PFontOpr ptFontOpr)
{
	ptFontOpr->ptNext = g_ptFonts;
	g_ptFonts = ptFontOpr;
}

这个没啥好说的,就是形成结构体FontOpr链表,每一种链接就是一种字符库。

函数FontsRegiste()的分析

void FontsRegister(void)
{
	FreetypeRegister();
}

这个函数去调用font\freetype.c中的函数FreetypeRegister()实现freetype字库注册进链表中。

void FreetypeRegister(void)
{
	RegisterFont(&g_tFreetypeOpr);
}
void RegisterFont(PFontOpr ptFontOpr)
{
	ptFontOpr->ptNext = g_ptFonts;
	g_ptFonts = ptFontOpr;
}

函数SelectAndInitFont()的分析

int SelectAndInitFont(char *aFontOprName, char *aFontFileName)
{
	PFontOpr ptTmp = g_ptFonts;
	while (ptTmp)
	{
		if (strcmp(ptTmp->name, aFontOprName) == 0)
			break;
		ptTmp = ptTmp->ptNext;
	}

	if (!ptTmp)
		return -1;

	g_ptDefaulFontOpr = ptTmp;
	return ptTmp->FontInit(aFontFileName);
}

字库选择并进行字库初始化。aFontOprName是字库名、aFontFileName是字库文件名。

文件display\disp_manager.c的分析

这个文件其实在嵌入式应用实例→电子产品量产工具→显示系统的代码阅读和上机测试记录中已经分析了,但是这里为了适应FreeType的需要,还需要增加函数DrawFontBitMap()

void DrawFontBitMap(PFontBitMap ptFontBitMap, unsigned int dwColor)
{
    int i, j, p, q;
	int x = ptFontBitMap->tRegion.iLeftUpX;
	int y = ptFontBitMap->tRegion.iLeftUpY;
    int x_max = x + ptFontBitMap->tRegion.iWidth;
    int y_max = y + ptFontBitMap->tRegion.iHeigh;
	int width = ptFontBitMap->tRegion.iWidth;
	unsigned char *buffer = ptFontBitMap->pucBuffer;

    //printf("x = %d, y = %d\n", x, y);

    for ( j = y, q = 0; j < y_max; j++, q++ )
    {
        for ( i = x, p = 0; i < x_max; i++, p++ )
        {
            if ( i < 0      || j < 0       ||
                i >= g_tDispBuff.iXres || j >= g_tDispBuff.iYres )
            continue;

            //image[j][i] |= bitmap->buffer[q * bitmap->width + p];
            if (buffer[q * width + p])
	            PutPixel(i, j, dwColor);
        }
    }
	
}

说白了,其实就是生成一个字符的位图文件后,我们需要遍历其中的每一个点并调用函数PutPixel()显示在LCD屏上,两个循环就是遍历的过程。
注意,下面这段代码是处理当要处理的位置是超过屏幕分辨率时怎么办的代码,从这个代码不难看出是进行丢弃处理。

            if ( i < 0      || j < 0       ||
                i >= g_tDispBuff.iXres || j >= g_tDispBuff.iYres )
            continue;

主函数(测试文件unittest\font_test.c 的分析)

测试文件里当然主要是主函数了…

int main(int argc, char **argv)
{
	PDispBuff ptBuffer;
	int error;

	FontBitMap tFontBitMap;
	char *str= "DayDayUp_SuWenhao";
	int i = 0;
	int lcd_x;
	int lcd_y;
	int font_size;
		

	if (argc != 5)
	{
		printf("Usage: %s <font_file> <lcd_x> <lcd_y> <font_size>\n", argv[0]);
		return -1;
	}

	lcd_x = strtol(argv[2], NULL, 0);
	lcd_y = strtol(argv[3], NULL, 0);
	
	font_size  = strtol(argv[4], NULL, 0);
	
		
	DisplayInit();

	SelectDefaultDisplay("fb");

	InitDefaultDisplay();

	ptBuffer = GetDisplayBuffer();

	FontsRegister();
	
	error = SelectAndInitFont("freetype", argv[1]);
	if (error)
	{
		printf("SelectAndInitFont err\n");
		return -1;
	}
	
	SetFontSize(font_size);

	while (str[i])
	{
		/* get bitmap */
		tFontBitMap.iCurOriginX = lcd_x;
		tFontBitMap.iCurOriginY = lcd_y;
		error = GetFontBitMap(str[i], &tFontBitMap);
		if (error)
		{
			printf("SelectAndInitFont err\n");
			return -1;
		}

		/* draw on buffer */		
		DrawFontBitMap(&tFontBitMap, 0xff0000);

		/* flush to lcd/web */		
		FlushDisplayRegion(&tFontBitMap.tRegion, ptBuffer);
		

		lcd_x = tFontBitMap.iNextOriginX;
		lcd_y = tFontBitMap.iNextOriginY;	
		i++;
	}
	
	return 0;	
}

主函数有五个参数,分别是程序名、字库文件名、绘制字符串的起始位置的x坐标、绘制字符串的起始位置的y坐标、字体大小。

LCD(Framebuffe)设备的初始化

因为我们要用到LCD显示屏显示字符,所以首先需要对LCD屏进行初始化,相关代码如下:

	DisplayInit();

	SelectDefaultDisplay("fb");

	InitDefaultDisplay();

	ptBuffer = GetDisplayBuffer();

注意,这里:

ptBuffer = GetDisplayBuffer();

实际上是得到的是虚拟内存的地址,其实是Framebuffer设备映射进虚拟内存的地址,你要某个点显示某个颜色值就把这块内存区域的四个字节的数据写成对应的值。

FreeType的初始化

	error = SelectAndInitFont("freetype", argv[1]);
	if (error)
	{
		printf("SelectAndInitFont err\n");
		return -1;
	}
	
	SetFontSize(font_size);

绘制每一个字符

	while (str[i])
	{
		/* get bitmap */
		tFontBitMap.iCurOriginX = lcd_x;
		tFontBitMap.iCurOriginY = lcd_y;
		error = GetFontBitMap(str[i], &tFontBitMap);
		if (error)
		{
			printf("SelectAndInitFont err\n");
			return -1;
		}

		/* draw on buffer */		
		DrawFontBitMap(&tFontBitMap, 0xff0000);

		/* flush to lcd/web */		
		FlushDisplayRegion(&tFontBitMap.tRegion, ptBuffer);
		

		lcd_x = tFontBitMap.iNextOriginX;
		lcd_y = tFontBitMap.iNextOriginY;	
		i++;
	}

注意:这里面语句FlushDisplayRegion(&tFontBitMap.tRegion, ptBuffer);实际上是没有用的,因为咱们这里用到的LCD显示设备其实并不需要刷新。

交叉编译

由于整个工程的源代码中用到了tslib库的freetype库,所以请先确认这两个库的头文件和动态链接库已经在交叉编译工具链中配置好了。
可参考下面两篇博文:
https://blog.youkuaiyun.com/wenhao_ir/article/details/144621008
https://blog.youkuaiyun.com/wenhao_ir/article/details/144751464

然后把Makefile文件改好…注意要添加freetype库头文件的路径~同时还要指定库lts和库lfreetype的库路径:
在这里插入图片描述

CFLAGS := -Wall -O2 -g
CFLAGS += -I $(shell pwd)/include
CFLAGS += -I $(shell pwd)/include/freetype2

LDFLAGS := -L/home/book/usedlib/tslib-1.21/tmp/lib -lts \
           -L/home/book/usedlib/freetype-2.10.2/tmp/lib -lfreetype \
           -lpthread

源码复制到Ubuntu中,并解压…
在这里插入图片描述

make

重命名生成的文件为font_unittest,并把文件simsun.ttc一并复制到NFS文件中,备用。
在这里插入图片描述
为了让屏幕黑屏,我们不妨把黑屏的可执行程序也复制进去:
(黑屏程序的生成见 https://blog.youkuaiyun.com/wenhao_ir/article/details/144594705
在这里插入图片描述

板上测试

打开串口,启动开发板,挂载网络文件系统:

mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
cd /mnt/free_type_unitest

给两个文件添加执行权限:

chmod +x draw_lcd_black
chmod +x font_unittest

运行程序 draw_lcd_black 把LCD屏黑屏

./draw_lcd_black

然后运行FreeType测试程序:

./font_unittest ./simsun.ttc 60 200 50

./simsun.ttc是字库文件的路径;
60是起始点的x坐标;
200起始点的y坐标;
50是字体大小。
在这里插入图片描述

附完整工程文件

https://pan.baidu.com/s/1jwl7hBvty2W6fDWGiTyzgw?pwd=3g6r

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

昊虹AI笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值