正点原子Linux学习笔记(九)在 LCD 上显示字符

在 LCD 上显示字符

  • 23.1 原始方式:取模显示字符
  • 23.2 freetype 简介
  • 23.3 freetype 移植
    • 下载 FreeType 源码
    • 交叉编译 FreeType 源码
    • 安装目录下的文件
    • 移植到开发板
  • 23.4 freetype 库的使用
    • 初始化 FreeType 库
    • 加载 face 对象
    • 设置字体大小
    • 加载字形图像
  • 23.5 示例代码

前面几个章节向大家介绍了如何在 LCD 屏上显示图像,本章我们就来学习下,如何在 LCD 屏上显示字符,譬如数字、字母以及中文字符等,相信很多读者都已经迫不及待的想要了解了。OK,废话不多说直接开干!
本章将会讨论如下主题。
⚫ 使用原始的方式:自己取模显示字符
⚫ 使用 freetype 访问字体文件;
⚫ freetype 简介;
⚫ freetype 移植;
⚫ freetype 的使用介绍。

23.1 原始方式:取模显示字符

LCD 显示屏是由 width * height 个像素点构成的,显示字符,一个非常容易想到的方法便是对字符取模,然后在 LCD 屏上打点显示字符;如果大家以前学习过单片机,想必接触过一些显示屏,譬如 oled、或者其它一些点阵式的显示屏,其实这些显示屏显示字符的原理都是一样的,如下所示:
在这里插入图片描述
我们可以通过一些字符取模软件获取到字符的子模;所谓子模,其实就是一个二维数组,用于表示字符点阵中,哪些小方块应该要填充颜色、哪些小方块不填充颜色。譬如上图“正”字符点阵,这是一个宽度为64(64 个小方块)、高度为 86(86 个小方块)的字符点阵,我们会使用一个二维数组来表示这个字符点阵:

unsigned char arr[86][8];

也就是一个 86 行 8 列的 unsigned char 类型数组,数组存储的其实就是字符的位图数据,字符点阵中的每一个小方块对应一个 bit 位,因为一行一共有 64 个小方块、也就对应 8 个字节(8 * 8 = 64);将填充颜色的方块使用 1 表示、不填充颜色的方块使用 0 来表示,所以一个小方块刚好可以使用一个 bit 位来描述。

以上给大家简单地介绍了字符点阵的问题,相信绝大部分读者都知道这些基础的东西,其实本不太想讲这些内容,但是考虑到可能有些读者确实就真的没接触这些,所以还是简单地提一下。我们编写一个简单的程序去测试下,网上有很多的这种字符取模的小软件,大家可以找一下,我们用这个取模软件,获取几个字符的子模,然后在我们的 LCD 屏上去显示这些字符。

23.2 freetype 简介

FreeType 一个完全免费(开源)的软件字体引擎库,设计小巧、高效、高度可定制且可移植,它提供了统一的接口来访问多种不同格式的字体文件。它提供了一个简单、易于使用且统一的接口来访问字体文件的内容,从而大大简化了这些任务。
请注意,“FreeType”也称为“FreeType 2”,以区别于旧的、已弃用的“FreeType 1”库,Freetype 1库已经不再维护和支持了。

23.3 freetype 移植

本小节我们来移植 FreeType,将 FreeType 移植到我们的开发板根文件系统中;事实上,开发板出厂系统已经移植好了这些库,但是版本稍微太低了,作为学习者,我们应该要学会自己动手移植,这个是很重要的工作;从上几个章节的学习内容可知,我们很多的编程工作都是基于别人写好的库、调用库函数来实现应用程序的功能,因为 Linux 软件生态做得非常好,有很多免费、开源的库是可以使用的,不用自己去捣鼓这些东西;所以作为一个嵌入式 Linux 软件开发人员,学会移植就显得很重要了。

下载 FreeType 源码

开发板出厂系统中,FreeType 的版本为 2.6,这个版本稍微有点低,我们选择移植 2.8 版本的 FreeType。进入到 https://download.savannah.gnu.org/releases/freetype/链接地址,如下所示:
在这里插入图片描述
在这里插入图片描述
大家要自己动手下载,开发板资料盘中是没有提供这些源码包的,包括前面几个章节介绍的库,譬如libpng、libjpeg 以及 tslib 等,都没有提供它们的源码压缩文件。

交叉编译 FreeType 源码

将下载好的 freetype-2.8.tar.gz 压缩文件拷贝到 Ubuntu 系统的用户家目录下,如下所示:
在这里插入图片描述
在 tools 目录下创建一个名为 freetype 的目录,把它作为 FreeType 的安装目录:
在这里插入图片描述
执行命令将 freetype-2.8.tar.gz 解压开来:

tar -xzf freetype-2.8.tar.gz

在这里插入图片描述
解压成功之后便会得到 FreeType 的源码目录 freetype-2.8。
进入到 freetype-2.8 目录,老规矩,同样是三部曲:配置、编译、安装。首先对交叉编译工具的环境进行初始化,前面章节内容已经提过很多次了,使用交叉编译器之前,必须要对其环境进行初始化(如果当前终端已经初始化过了,则无需再次进行初始化)。

FreeType 库基于模块化设计,意味着我们可以对其进行裁剪,将不需要的功能模块从配置中移除,减小库文件的体积;除此之外,FreeType 还支持很多配置选项,如果大家想要对 FreeType 做一些自定义配置或者对其进行裁剪,可以参考 FreeType 源码目录下 docs/CUSTOMIZE 文档,该文件对此有比较详细的说明,建议大家看一看,如果有需求的话。docs 目录下还有其它很多的说明文档,也都可以读一读。这里我们简单地配置一下,打开 include/freetype/config/ftoption.h 文件,如下所示:

vi include/freetype/config/ftoption.h

在这里插入图片描述
该文件定义了很多的配置宏,我们可以选择使能或禁用这些配置选项,具体配置哪些功能,大家自己去研究,每一个配置宏都有详细地解释说明。这里我们打开以下两个配置宏:

#define FT_CONFIG_OPTION_SYSTEM_ZLIB
#define FT_CONFIG_OPTION_USE_PNG

大家找到这两个宏,默认情况下,这两个都被注释掉了,所以是没有使能的;把这两个宏的注释去掉,使能这两个配置宏。

第一个配置宏表示使用系统安装的 zlib 库,因为 FreeType 支持 Gzip 压缩文件,会使用到 zlib 库,zlib之前我们移植好了;第二个配置宏表示支持 PNG bitmap 位图,因为 FreeType 可以加载 PNG 格式的彩色位图字形,需要依赖于 libpng 库,这个库前面我们也是移植好了。

配置好之后,保存、退出 ftoption.h 文件,接着执行如下命令对 FreeType 工程源码进行配置:

/configure --prefix=/home/dt/tools/freetype/ --host=arm-poky-linux-gnueabi --with-zlib=yes --with-bzip2=no - -with-png=yes --with-harfbuzz=no ZLIB_CFLAGS="-I/home/dt/tools/zlib/include -L/home/dt/tools/zlib/lib" 
ZLIB_LIBS=-lz LIBPNG_CFLAGS="-I/home/dt/tools/png/include -L/home/dt/tools/png/lib" LIBPNG_LIBS=-
lpng

这个配置命令很长,简单地提一下,具体的细节大家可以执行"./configure --help"查看配置帮助信息。
–prefix 选项指定 FreeType 库的安装目录;–host 选项设置为交叉编译器名称的前缀,这两个选项前面几个章节内容都已经给大家详细地解释过。
–with-zlib=yes 表示使用 zlib;
–with-bzip2=no 表示不使用 bzip2 库;
–with-png=yes 表示使用 libpng 库;
–with-harfbuzz=no 表示不使用 harfbuzz 库。
ZLIB_CFLAGS 选项用于指定 zlib 的头文件路径和库文件路径,根据实际安装路径填写;
ZLIB_LIBS 选项指定链接的 zlib 库的名称;
LIBPNG_CFLAGS 选项用于指定 libpng 的头文件路径和库文件路径,根据实际安装路径填写;
LIBPNG_LIBS 选项用于指定链接的 libpng 库的名称。
在这里插入图片描述
配置完成之后接着执行 make 编译,编译完成之后执行 make install 安装即可!

安装目录下的文件

进入到 FreeType 安装目录下,如下所示:
在这里插入图片描述
同样有 bin 目录、include 目录以及 lib 目录,大家可以自己进入到这些目录下,浏览下这些目录下的有哪些文件,对此有个印象。
如果要使用 FreeType 库,我们需要在应用程序源码中包含 include/freetype2 目录下的 ft2build.h 头文件,除此之外,还需要包含另一个头文件 FT_FREETYPE_H,这是一个用宏定义的头文件,其实就是include/freetype2/freetype/freetype.h 头文件。所以,在我们的应用程序一般是这样写:

#include <ft2build.h>
#include FT_FREETYPE_H

移植到开发板

接下来将编译得到的动态链接库文件拷贝到开发板 Linux 系统/usr/lib 目录,在拷贝之前,需将/usr/lib 目录下原有的 FreeType 库文件删除掉,执行下面这条命令:

rm -rf /usr/lib/libfreetype.*

删除之后,再将我们编译得到的库文件拷贝到开发板/usr/lib 目录下,也就是 FreeType 安装目录 lib 目录下的所有库文件,拷贝的时候注意符号链接的问题。拷贝完成之后,如下所示:
在这里插入图片描述

23.4 freetype 库的使用

整个移植工作完成之后,接着简单地介绍下 FreeType 库的使用,FreeType 库支持的功能很多、提供给用户的库函数也很多,所以笔者肯定不会给大家细聊!以介绍性为主。
FreeType 官方也提供了详细地使用帮助文档,以下便是这些文档的链接地址:
https://www.freetype.org/freetype2/docs/tutorial/step1.html
https://www.freetype.org/freetype2/docs/tutorial/step2.html
https://www.freetype.org/freetype2/docs/reference/index.html
以下这个链接是一份中文参考文档,大家可以看一下,笔者也不知道是哪位作者编写的,写的非常详细!
https://www.doc88.com/p-7178359224563.html?r=1
在正式介绍 FreeType 库使用之前,需要先了解几个涉及到的概念:

字形(glyph)
字符图像就叫做字形,一个字符能够有多种不同的字形,可以理解为字形就是字符的一种书写风格,譬如宋体的汉字“国”与微软雅黑的汉字“国”,它们的字形是不同的,也就是它们书写风格是不同;宋体的“国”与微软雅黑的“国”就是两种不同的字形。
字形索引
在字体文件中,通过字形索引找到对应的字形,而字形索引是由字符编码转换而来的,譬如 ASCII 编码、GB2312 编码、BIG5 编码、GBK 编码以及国际标准字符集使用的 Unicode 编码等。对于字符编码,如果还有不了解读者,建议自行查阅相关的书籍。
像素点(pixel)、点(point)以及 dpi
像素点大家都知道,譬如 LCD 分辨率为 800480,那就表示 LCD 水平方向有 800 个像素点、垂直方向有 480 个像素点,所以此 LCD 一共有 800480 个像素点。像素点这个概念大家都很熟悉了,也就不再多说;我们再来看下“点”的概念,点(point)是一种简单地物理单位,在数字印刷中,一个点(point)等于 1/72 英寸(1 英寸等于 25.4 毫米)。除此之外,还有一个 dpi 的概念,dpi(dots per inch)表示每英寸的像素点数,譬如 300*400dpi 表示在水平方向,每英寸有 300 个像素点、在垂直方向上每英寸有 400 个像素点。通过点数和 dpi 可以计算出像素点数,公式如下:

像素点数 = 点数 * dpi / 72

譬如,假设某一显示设备水平方向 dpi 为 300,已知水平方向的点数为 50,那么像素点数的计算方式为:
50 * 300 / 72 = 208
所以可以算出像素点数为 208,因为后面会用到这些概念,所以先给大家简单地说明一下。

字形的布局

以下两张图清晰地描述了字形布局的情况:分为水平布局和垂直布局,分别使用图 23.4.1 和图 23.4.2来描述布局情况,以下这两张图都是从官方的文档中截取过来的。

水平方向书写文字使用水平布局方式,绝大部分情况下我们一般都是在水平方向上书写文字;垂直方向书写文字使用垂直布局方式,对于汉字来说,垂直方向书写也是比较常见的,很有代表性的就是对联、还有很多古书文字的写法,也都是采用这种垂直书写。
在这里插入图片描述
在这里插入图片描述
⚫ 基准线、原点
从图中可以看到,不管是水平布局还是垂直布局,图中都可以找到一个 origin 原点,经过原点的水平线(X 轴)和垂直线(Y 轴)称为基准线,笔者将其称为水平基线和垂直基线。
对于水平布局,垂直基线在字形的左边,垂直基线简单地放置在字形上,通过图中所标注的度量数据确定与基线的位置关系。
对于垂直布局,水平基线在字形的上方,字形在垂直基线上居中放置,同样也是通过图中所标注的度量数据确定与基线的位置关系。原点、基准线可以用于定位字形,水平布局和垂直布局使用不同的约束来放置字形。

⚫ 字形的宽度和高度
每一个字形都有自己的宽度和高度,图中使用 width(宽)和 height(高)来表示,width 描述了字形轮廓的最左边到最右边的距离;而 height 描述了字形轮廓的最上边到最下边的距离。同一种书写风格,不同字符所对应的字形,它们的宽高是不一定相等的,譬如大写 A 和小写 a,宽度和高度明显是不同的;但有些字符的字形宽度和高度是相同的,这个与具体的字符有关!

⚫ bearingX 和 bearingY
bearingX 表示从垂直基线到字形轮廓最左边的距离。对于水平布局来说,字形在垂直基线的右侧,所以bearingX 是一个正数;而对于垂直布局来说,字形在垂直基线上居中放置,所以字形轮廓的最左边通常是在垂直基线的左侧,所以 bearingX 是一个负数。bearingY 则表示从水平基线到字形轮廓最上边的距离。对于垂直布局来说,bearingY 是一个正数,字形处于水平基线的下方;而对于水平布局来说,如果字形轮廓的最上边在水平基线的上方,则 bearingY 是一个正数、相反则是一个负数。

⚫ xMin/xMax、yMin/yMax
xMin 表示字形轮廓最左边的位置,xMax 则表示字形轮廓最右边的位置;yMin 表示字形轮廓最下边的位置,yMax 则表示字形轮廓最上边的位置,通过这 4 个位置可以构成一个字形的边界框(bounding box,bbox),当然这是一个假象的框子,它尽可能紧密的装入字形。

⚫ advance
advance 则表示步进宽度,相邻两个原点位置的距离(字间距)。如果是水平布局,则表示相邻的两个原点在水平方向上的距离(advanceX),也就是相邻两条垂直基线之间的距离;同理,如果是垂直布局,则表示相邻的两个原点在垂直方向上的距离(advanceY),也就是相邻两条水平基线之间的距离。

以上所提到的这些参数都很重要,大家一定要理解这些参数所表示的意义,绘制字符时,需要以这些参数作为参考值进行对齐显示。
使用 FreeType 访问字体文件,可以从字体文件中获取到字形的位图数据,位图数据存储在一个 buffer中,buffer 大小为字形的宽高个字节(字形边界框的宽高个字节),也就是图 23.4.1 中 width*height 个字节大小,每一个点使用一个字节来表示,当数组中该点对应的数值等于 0,表示该点不填充颜色;当数值大 于 0,表示该点需要填充颜色。

字符显示时如何对齐?
平时我们使用文本编辑器编写文字的时候,这些字符都是对齐显示的;譬如在一行文本中,即使包含大小写的英文字母、标点符号、汉字等这些字符,这一行字符显示在屏幕上时、都是对齐显示的;这里说的对齐是按照标准规范进行对齐,譬如逗号","显示时是靠近下边的、而不是靠近上边显示;双引号“”显示时是靠近上边的、而不是居中显示;这就是笔者认为的字符显示时的对齐规范,你可以认为每一个字符,它都有对应的一个显示规范,是靠近上边显示呢、还是靠近下边显示亦或者是靠近中间显示呢等。那我们如何保证对齐显示呢?其实就是通过图 23.4.1 中的水平基线和垂直基线,如下图所示:
在这里插入图片描述
不同字符对应的字形,水平基线到字形轮廓最上边的距离都是不一样的,譬如图中水平基线到“A”和“a”轮廓最上边的距离明显是不一样的;除此之外,有些字形的轮廓最下边已经在水平基线之下、而有些字形的轮廓最下边却又在水平基线之上。

分析完水平基线之后,我们再来看看垂直基线,每一个字形的垂直基线到字形轮廓最左边的距离也都是不一样的,譬如“韩”和“a”,很明显、它们各自的垂直基线到字形轮廓最左边的距离是不一样的。

水平基线可以作为垂直方向(上下方向)上对齐显示的基准线,而垂直基线可以作为水平方向(左右方向)上对齐显示的基准线;对于水平布局来说,相邻两条垂直基线的距离就是字间距或者叫步进宽度;从一个字形的原点加上一个步进宽度就到了下一个字形的原点。

当我们要在屏幕上画字形的时候,首先要定位到字形的左上角位置,从左上角开始,依次从左到右、从上到下,字形显示的宽度就是字形的宽度 width、字符显示的高度就是字形的高度 height。那如何找到左上角的位置,这个很简单,通过 bearingY 和 bearingX 便可确定。譬如我们将(100, 100)这个位置作为原点,那么,字符显示位置的左上角便是(100+bearingX, 100-bearingY)。

以上就给大家介绍关于字符对齐的问题,大家一定要理解这些内容,如果你理解不了上面的内容,后面的示例代码你可能就看不懂。

初始化 FreeType 库

在使用 FreeType 库函数之前,需要对 FreeType 库进行初始化操作,使用 FT_Init_FreeType()函数完成初始化操作。在调用该函数之前,我们需要定义一个 FT_Library 类型变量,调用 FT_Init_FreeType()函数时将该变量的指针作为参数传递进去;使用示例如下所示:

FT_Library library;
FT_Error error;
error = FT_Init_FreeType(&library);
if (error)
fprintf(stderr, "Error: failed to initialize FreeType library object\n");

FT_Init_FreeType 完成以下操作:
⚫ 它创建了一个 FreeType 库对象,并将 library 作为库对象的句柄。
⚫ FT_Init_FreeType()调用成功返回 0;失败将返回一个非零值错误码。

加载 face 对象

应用程序通过调用 FT_New_Face()函数创建一个新的 face 对象,其实就是加载字体文件,为啥叫 face(脸),应该是一种抽象的说法!一个 face 对象描述了一个特定的字体样式和风格,譬如"Times New Roman Regular"和"Times New Roman Italic"对应两种不同的 face。
调用 FT_New_Face()函数前,我们需要定义一个 FT_Face 类型变量,使用示例如下所示:

FT_Library library; //库对象的句柄
FT_Face face; //face 对象的句柄
FT_Error error;
FT_Init_FreeType(&library);
error = FT_New_Face(library, "/usr/share/fonts/font.ttf", 0, &face);
if (error) {
/* 发生错误、进行相关处理 */
}

}
FT_New_Face()函数原型如下所示:

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

函数参数以及返回值说明如下:
library:一个 FreeType 库对象的句柄,face 对象从中建立;
filepathname:字库文件路径名(一个标准的 C 字符串);
face_index:某些字体格式允许把几个字体 face 嵌入到同一个文件中,这个索引指示了你想加载的 face,
其实就是一个下标,如果这个值太大,函数将会返回一个错误,通常把它设置为 0 即可!想要知道一个字体
文件中包含了多少个 face,只要简单地加载它的第一个 face(把 face_index 设置为 0),函数调用成功返回
后,face->num_faces 的值就指示出了有多少个 face 嵌入在该字体文件中。
aface:一个指向新建 face 对象的指针,当失败时其值被设置为 NULL。
返回值:调用成功返回 0;失败将返回一个非零值的错误码。

设置字体大小

设置字体的大小有两种方式:FT_Set_Char_Size()和 FT_Set_Pixel_Sizes()。
FT_Set_Pixel_Sizes()函数
调用 FT_Set_Pixel_Sizes()函数设置字体的宽度和高度,以像素为单位,使用示例如下所示:

FT_Set_Pixel_Sizes(face, 50, 50);

第一个参数传入 face 句柄;第二个参数和第三个参数分别指示字体的宽度和高度,以像素为单位;需
要注意的是,我们可以将宽度或高度中的任意一个参数设置为 0,那么意味着设置为 0 的参数将会与另一个参数保持相等,如下所示:

FT_Set_Pixel_Sizes(face, 50, 0);

上面调用 FT_Set_Pixel_Sizes()函数时,将字体高度设置为 0,也就意味着字体高度将自动等于字体宽度50。

FT_Set_Char_Size()函数
调用 FT_Set_Char_Size()函数设置字体大小,示例如下所示,假设在一个 300x300dpi 的设备上把字体大小设置为 16pt:

error = FT_Set_Char_Size(
face,//face 对象的句柄
16*64, //以 1/64 点为单位的字体宽度
16*64, //以 1/64 点为单位的字体高度
300, //水平方向上每英寸的像素点数
300); //垂直方向上每英寸的像素点数

说明:
⚫ 字体的宽度和高度并不是以像素为单位,而是以 1/64 点(point)为单位表示(也就是 26.6 固定浮点格式),一个点是一个 1/72 英寸的距离。
⚫ 同样也可将宽度或高度其中之一设置为 0,那么意味着设置为 0 的参数将会与另一个参数保持相等。
⚫ dpi 参数设置为 0 时,表示使用默认值 72dpi。

加载字形图像

设置完成之后,接下来就可以加载字符图像了,总共分为 3 步:
a)、获取字符的字形索引
通过 FT_Get_Char_Inde()函数将字符编码转换为字形索引(glyph index),Freetype 默认使用 UTF-16 编码类型,也就是 Unicode 编码方式,采用 2 个字节来表示一个编码值。
XXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXX

23.5 示例代码

显示单个 ASCII 字符
显示单个中文字符
显示一行字符(一行字符对齐显示)
显示多行字符(一行字符的高度计算)
字体变形(旋转、斜体………)

示例代码 23.5.1 FreeType 使用示例代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <math.h> //数学库函数头文件
#include <wchar.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#define FB_DEV "/dev/fb0" //LCD 设备节点
#define argb8888_to_rgb565(color) ({ \
 unsigned int temp = (color); \
 ((temp & 0xF80000UL) >> 8) | \
 ((temp & 0xFC00UL) >> 5) | \
 ((temp & 0xF8UL) >> 3); \
 })
static unsigned int width; //LCD 宽度
static unsigned int height; //LCD 高度
static unsigned short *screen_base = NULL;//LCD 显存基地址 RGB565
static unsigned long screen_size;
static int fd = -1;
static FT_Library library;
static FT_Face face;
static int fb_dev_init(void)
{
 struct fb_var_screeninfo fb_var = {0};
 struct fb_fix_screeninfo fb_fix = {0};
 /* 打开 framebuffer 设备 */
 fd = open(FB_DEV, O_RDWR);
 if (0 > fd) {
 fprintf(stderr, "open error: %s: %s\n", FB_DEV, strerror(errno));
 return -1;
 }
 /* 获取 framebuffer 设备信息 */
 ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
 ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);
 screen_size = fb_fix.line_length * fb_var.yres;
 width = fb_var.xres;
 height = fb_var.yres;
 /* 内存映射 */
 screen_base = mmap(NULL, screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
 if (MAP_FAILED == (void *)screen_base) {
 perror("mmap error");
 close(fd);
 return -1;
 }
 /* LCD 背景刷成黑色 */
 memset(screen_base, 0xFF, screen_size);
 return 0; }
static int freetype_init(const char *font, int angle) {
 FT_Error error;
 FT_Vector pen;
 FT_Matrix matrix;
 float rad; //旋转角度
 /* FreeType 初始化 */
 FT_Init_FreeType(&library);
 /* 加载 face 对象 */
 error = FT_New_Face(library, font, 0, &face);
 if (error) {
 fprintf(stderr, "FT_New_Face error: %d\n", error);
 exit(EXIT_FAILURE);
 }
 /* 原点坐标 */
 pen.x = 0 * 64;
 pen.y = 0 * 64; //原点设置为(0, 0)
 /* 2x2 矩阵初始化 */
 rad = (1.0 * angle / 180) * M_PI; //(角度转换为弧度)M_PI 是圆周率
#if 0 //非水平方向
 matrix.xx = (FT_Fixed)( cos(rad) * 0x10000L);
 matrix.xy = (FT_Fixed)(-sin(rad) * 0x10000L);
 matrix.yx = (FT_Fixed)( sin(rad) * 0x10000L);
 matrix.yy = (FT_Fixed)( cos(rad) * 0x10000L);
#endif
#if 1 //斜体 水平方向显示的
 matrix.xx = (FT_Fixed)( cos(rad) * 0x10000L);
 matrix.xy = (FT_Fixed)( sin(rad) * 0x10000L);
 matrix.yx = (FT_Fixed)( 0 * 0x10000L);
 matrix.yy = (FT_Fixed)( 1 * 0x10000L);
#endif
 /* 设置 */
 FT_Set_Transform(face, &matrix, &pen);
 FT_Set_Pixel_Sizes(face, 50, 0); //设置字体大小
 return 0; }
static void lcd_draw_character(int x, int y,
 const wchar_t *str, unsigned int color) {
 unsigned short rgb565_color = argb8888_to_rgb565(color);//得到 RGB565 颜色值
 FT_GlyphSlot slot = face->glyph;
 size_t len = wcslen(str); //计算字符的个数
 long int temp;
 int n;
 int i, j, p, q;
 int max_x, max_y, start_y, start_x;
  // 循环加载各个字符
 for (n = 0; n < len; n++) {
 // 加载字形、转换得到位图数据
 if (FT_Load_Char(face, str[n], FT_LOAD_RENDER))
 continue;
 start_y = y - slot->bitmap_top; //计算字形轮廓上边 y 坐标起点位置 注意是减去 bitmap_top
 if (0 > start_y) {//如果为负数 如何处理??
 q = -start_y;
 temp = 0;
 j = 0;
 }
 else { // 正数又该如何处理??
 q = 0;
 temp = width * start_y;
 j = start_y;
 }
 max_y = start_y + slot->bitmap.rows;//计算字形轮廓下边 y 坐标结束位置
 if (max_y > (int)height)
 max_y = height;
 for (; j < max_y; j++, q++, temp += width) {
 start_x = x + slot->bitmap_left; //起点位置要加上左边空余部分长度
 if (0 > start_x) {
 p = -start_x;
 i = 0;
 }
 else {
 p = 0;
 i = start_x;
 }
 max_x = start_x + slot->bitmap.width;
 if (max_x > (int)width)
 max_x = width;
 for (; i < max_x; i++, p++) {
 // 如果数据不为 0,则表示需要填充颜色
 if (slot->bitmap.buffer[q * slot->bitmap.width + p])
 screen_base[temp + i] = rgb565_color;
 }
 }
 //调整到下一个字形的原点
 x += slot->advance.x / 64; //26.6 固定浮点格式
 y -= slot->advance.y / 64;
 } }
int main(int argc, char *argv[])
{
 
 /* LCD 初始化 */
 if (fb_dev_init())
 exit(EXIT_FAILURE);
 /* freetype 初始化 */
 if (freetype_init(argv[1], atoi(argv[2])))
 exit(EXIT_FAILURE);
 /* 在 LCD 上显示中文 */
 int y = height * 0.25;
 lcd_draw_character(50, 100, L"路漫漫其修远兮,吾将上下而求索", 0x000000);
 lcd_draw_character(50, y+100, L"莫愁前路无知己,天下谁人不识君", 0x9900FF);
 lcd_draw_character(50, 2*y+100, L"君不见黄河之水天上来,奔流到海不复回", 0xFF0099);
 lcd_draw_character(50, 3*y+100, L"君不见高堂明镜悲白发,朝如青丝暮成雪", 0x9932CC);
 /* 退出程序 */
 FT_Done_Face(face);
 FT_Done_FreeType(library);
 munmap(screen_base, screen_size);
 close(fd);
 exit(EXIT_SUCCESS);
}

编译方法:

${CC} -o testApp testApp.c -I/home/dt/tools/freetype/include/freetype2 -L/home/dt/tools/freetype/lib -
lfreetype -L/home/dt/tools/zlib/lib -lz -L/home/dt/tools/png/lib -lpng -lm

效果图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/613978.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

国产根SSL证书,验证签发数据不出境

在探讨SSL证书数据是否可能“出境”的问题之前&#xff0c;我们需要先理解几个基本概念&#xff1a;什么是SSL证书、数据传输的基本流程&#xff0c;以及“出境”在此语境下的含义。本文旨在以科普的方式&#xff0c;清晰地解析这一主题&#xff0c;帮助读者建立起对SSL证书及其…

upload组件封装,支持拖拽文件上传

一、组件封装需要注意什么? 组件化思想:组件应该是独立的、可复用的部件,应该遵循单一职责原则,将组件的功能划分得尽可能细致。 API 设计:组件的 API 设计要合理,要考虑到组件的可定制性和易用性。应该尽可能的提供必要的配置项和事件回调,同时避免提供过多的 API,导…

【启明智显分享】国产自主HMI核心板Model3

Model3是一款高性能的工业级HMI&#xff08;人机界面&#xff09;核心板&#xff0c;也是一款纯国产HMI方案&#xff0c;工业级标准&#xff0c;稳定、可靠&#xff1b; 工业级HMI芯片–Model3 纯国产HMI方案 Model3核心板&#xff0c;具有2D加速&#xff0c;PNG解码&…

高效电源测试设备助力自动化测试和数据分析

在当今电子产品的研发和生产过程中&#xff0c;电源测试设备的重要性不言而喻。一款优秀的电源测试设备能够显著提升测试效率&#xff0c;确保电源模块的性能达到设计要求。 纳米软件NSAT-8000电源测试系统是一款自动化电源测试设备&#xff0c;在测试电源模块时&#xff0c;通…

ESP32 + ST7789 LCD

1、准备 ESP32 单片机开发板 ST7789 LCD 模块&#xff08;240 * 320 像素&#xff09; 杜邦线 2、接线 LCD功能ESP32VCC 供电电压正极 3.3V 、 5V GND 供电电压负极 GNDIDN / MOSI SPI 接口数据 引脚 23CLK 串行接口时钟信号 18CS 芯片选择引脚&#xff1b;低电平有效 5DC 显…

监控员工上网用什么软件,4款优秀的上网行为监控软件优选

很多员工都会对工作有懈怠心理。 在数字化办公环境中&#xff0c;员工的上网行为直接影响着工作效率、信息安全与合规运营。 为确保企业资源合理利用、防止潜在风险&#xff0c;上网行为监控软件成为企业管理的重要辅助工具。 本文将为您推荐五款优秀的上网行为监控软件&#…

基于STC12C5A60S2系列1T 8051单片机实现一主单片机给一从单片机发送数据的串口通信功能

基于STC12C5A60S2系列1T 8051单片机实现一主单片机给一从单片机发送数据的串口通信功能 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机串口通信介绍STC12C5A60S2系列1T 8051单片机串口通信的结构基于STC12C5A60S2系列1T 8051单片机串口通信的特殊功能寄存…

棱镜七彩参编《网络安全技术 软件供应链安全要求》国家标准发布

据全国标准信息公共服务平台消息显示&#xff0c;《网络安全技术 软件供应链安全要求》&#xff08;GB/T 43698-2024&#xff09;国家标准已于2024年4月25日正式发布&#xff0c;并将于2024年11月1日正式实施。棱镜七彩作为主要编制单位之一参与该国家标准的编制&#xff0c;为…

【Web后端】jsp基础知识_请求转发和重定向

1.jsp基础知识 1.1简介 java server page&#xff0c;运行在服务器端的页面java代码html代码java代码全部都放在<%%>中间 1.2jsp表达式 作用&#xff1a;将动态信息显示在页面上&#xff0c;以字符串方式&#xff0c;返回给浏览器端语法&#xff1a;<%变量或表达式…

itextpdf 7生成pdf(主要是文字和表格,支持中文)

我们经常会遇到要导出pdf的需求,方式有很多种 今天的教程是采用itextpdf的方式生成pdf itextpdf是用于生成PDF文档的一个java类库。通过iText不仅可以生成PDF文档&#xff0c;而且可以将Html文件转化为PDF文件。 这里先展示一下效果图 首先在pom.xml中引入相关依赖 <dep…

MotionDiffuse: Text-Driven Human Motion Generation withDiffusion Model # 论文阅读

URL https://arxiv.org/pdf/2208.15001 主页&#xff1a;https://mingyuan-zhang.github.io/projects/MotionDiffuse.html TD;DR 22 年 8 月商汤的文章&#xff0c;引用量 200。基于 SD&#xff0c;任务是输入文本的动作描述&#xff0c;生成对应的动作序列。 已有的 moti…

如何选择合适加密软件来保护信息资产|精选加密软件分析

五款加密软件对比分析&#xff0c;是一项复杂而必要的任务&#xff0c;旨在帮助用户选择最适合其需求的加密工具。在数字化时代&#xff0c;信息安全显得尤为重要&#xff0c;因此&#xff0c;对加密软件的评估与比较显得尤为关键。 首先&#xff0c;我们要考虑的是这些加密软件…

鸿蒙开发-ArkTS语言-XML

鸿蒙开发-UI-web 鸿蒙开发-UI-web-页面 鸿蒙开发-ArkTS语言-基础类库 鸿蒙开发-ArkTS语言-并发 鸿蒙开发-ArkTS语言-并发-案例 鸿蒙开发-ArkTS语言-容器 鸿蒙开发-ArkTS语言-非线性容器 文章目录 前言 一、XML概述 二、XML生成 三、XML解析 1.解析XML标签和标签值 2.解析XML属性…

ENVI实战—一文教会你开展辐射校正和大气校正的方法

实验1&#xff1a;辐射校正 目的&#xff1a;在ENVI中&#xff0c;掌握辐射校正的方法 过程&#xff1a; ①导入影像&#xff1a;在ENVI中按照“文件→打开为→光学传感器→Landsat→带有元数据的GeoTIFF”的过程&#xff0c;打开多光谱数据&#xff08;图1&#xff09;。 ②…

融合Transformer与CNN,实现各任务性能巅峰,可训练参数减少80%

论文er看过来&#xff0c;今天给各位推荐一个热门创新方向&#xff1a;CNNTransformer。 众所周知&#xff0c;CNN通过多层卷积自动学习空间层级特征&#xff0c;能够有效提取图像局部特征。而Transformer通过自注意力机制全局建模&#xff0c;能够有效处理长距离依赖关系。 …

MySQL innodb_buffer_pool_size 相关常用语句

对于MySQL速度慢的问题&#xff0c;除了优化 SQL 以外&#xff0c;应该必须优先想到的即使 MySQL 数据库的 innodb_buffer_pool_size 配置问题。 一般来说&#xff0c;innodb_buffer_pool_size 的默认大小都是很小的&#xff0c;尤其是 win 下其默认大小更是只有离谱的 8M。Li…

在做题中学习(54):点名

LCR 173. 点名 - 力扣&#xff08;LeetCode&#xff09; 此题有不同的几种解法&#xff1a; 解法一&#xff1a;暴力枚举 O(n); 解法二&#xff1a;哈希表 把原数组丢入哈希表&#xff0c;遍历哈希表&#xff0c;看看哪个数值为0即可。 O(n)空间O(n)时间 解法三&…

12、FreeRTOS信号量(semaphore)

文章目录 一、信号量的特性1.1 使用场景1.2 什么是信号量1.3 信号量和队列的区别1.4 两种信号量的对比 二、二值信号量/计数信号量2.1 什么是二值信号量2.2 什么是计数信号量2.2 (二值信号量/计数信号量) 相关API 三、互斥量(mutex)3.2 什么是优先级翻转3.3 互斥量的使用场合3.…

Kotlin基本特性

目录 函数 if when 循环 面向对象 继承 主构造函数 接口 修饰符 ​编辑数据类 单例类 Lambda编程 集合 lambda用法 常见函数式API 空指针 判空辅助工具 字符串内嵌表达式 函数 fun add1(a:Int,b:Int):Int{return ab }fun add2(a:Int,b:Int):Int ab // 只…

【JVM】ASM开发

认识ASM ASM是一个Java字节码操纵框架&#xff0c;它能被用来动态生成类或者增强既有类的功能。 ASM可以直接产生二进制class文件&#xff0c;也可以在类被加载入虚拟机之前动态改变类行为&#xff0c;ASM从类文件中读入信息后能够改变类行为&#xff0c;分析类信息&#xff…