这些内容均是留给自己用作学习笔记,如有错误,请批评指正!!!
文章目录
前言
这一章主要学习的了解FrameBuffer的使用,熟悉字符的编码格式,以及如何在LCD上显示一个字符和一行字符串。(本文并没有使用开发板,所有图片均来自网络)
一、为什么要使用FrameBuffer
Linux下的FrameBuffer是一种图形显示子系统,它允许用户对图形硬件进行低级别的访问,以便在屏幕上显示图形。FrameBuffer支持许多不同类型的图形硬件,并提供了一个称为/dev/fb的设备节点,用户可以通过该节点与硬件进行交互。FrameBuffer控制屏幕上的每个像素,允许图形应用程序在屏幕上绘制图像。它也支持像素级的操作,例如图像旋转、缩放和移动。由于FrameBuffer在硬件级别操作,因此它比传统的X窗口系统更快和更稳定,特别是在较慢的嵌入式设备上。总之,Linux FrameBuffer提供了一种在Linux系统上直接控制图形硬件的方法,使用户能够更快、更准确地将图形输出到屏幕上。
FrameBuffer的操作原理
LCD Framebuffer 就是一块显存,在嵌入式系统中,显存是被包含在内存中。LCD Framebuffer里的若干字节(根据驱动程序对LCD控制器的配置而定)表示LCD屏幕中的一个像素点,一一对应整个LCD屏幕。举个例子,LCD屏幕是800*600的分辨率,即LCD屏幕存在480000个像素点,若每个像素点4个字节表示,那么LCD Framebuffer显存大小为480000 *4=960000字节,即1.92MB。因此我们的内存将会分割至少1.92MB的空间用作显存。具体地址在哪里,这个就是又驱动程序去定,应用程序只需直接使用即可,硬件相关操作已由驱动程序封装好。
如上图,我们只需要往Framebuffer中填入不同的值,驱动程序和硬件控制器就会把这些数据传输到对应LCD屏幕上的像素点,从而显示不同的颜色。由此可知,我们应用程序只需要针对Framebuffer操作即可,其他交给驱动程序和硬件。
常用的API函数接口:
1.open
在上一章已经讲解过这个函数的作用了,具体参数以及返回值不再叙述。
文件IO
2.ioctl函数
参数 | 说明 |
---|---|
fd | 表示文件描述符 |
request | 表示与驱动程序交互的命令,用不同的命令控制驱动程序输出我们需要的数据 |
… | 表示可变参数arg,根据request命令,设备驱动程序返回输出的数据 |
在Linux系统中,ioctl()函数是用来发送控制命令给设备的系统调用接口。它是设备驱动程序的一个重要部分,通过该函数进行设备的初始化、控制、配置以及获取设备状态等相关操作。在使用FrameBuffer时,可以利用ioctl()函数发送一些控制命令,来设置FrameBuffer的一些属性,例如分辨率、颜色格式、可见区域等,从而实现对图形输出的控制。
3.mmap函数
参数 | 说明 |
---|---|
addr | 表示指定映射的內存起始地址,通常设为 NULL表示让系统自动选定地址,并在成功映射后返回该地址; |
length | 表示将文件中多大的内容映射到内存中 |
prot | 表示映射区域的保护方式 |
Flags | 表示影响映射区域的不同特性 |
prot :
①PROT_EXEC 映射区域可被执行
②PROT_READ 映射区域可被读写
③PROT_WRITE 映射区域可被写入
④PROT_NONE 映射区域不能存取
Flags :
①MAP_SHARED 表示对映射区域写入的数据会复制回文件内,原来的文件会改变。
②MAP_PRIVATE 表示对映射区域的操作会产生一个映射文件的复制,对此区域的任何修改都不会写回原来的文件内容中。
返回值:若成功映射,将返回指向映射的区域的指针,失败将返回-1。
在 Linux 中,mmap() 函数用于将一个文件或者其它对象映射到内存中,以便可以使用指针进行访问。它提供了一种非常高效的文件 I/O 方法,使用它可以避免大量的磁盘 I/O 操作,提高系统的 I/O 性能。
二、LCD描点步骤
点阵理论基础
如上图,当我们需要显示一个字母‘A’时,是通过判断点阵的每一个位数值状态,来填充颜色,达到显示字符效果。其中‘1’表示一种颜色,‘0’表示填充另一种颜色。上图的是8*16的点阵,我们也可以用其他不同大小点阵,只要有这个点阵,我们就可以在LCD上面描点,达到显示字符的效果。
获取fb_var_screeninfo结构体
在用点阵显示字符之前,我们需要先从设备fb0中获取相关的LCD信息
通过系统调用ioctl,获取xres(x方向总像素点),yres(y方向总像素点),bits_per_pixel(每个像素点占据的位数),根据获取的三个资源,外加点阵,根据这四个资源,我们就可以显示一个字符。
知道了这些信息后,我们就可以实现显示字符了。代码如下:
fd_fb = open("/dev/fb0", O_RDWR);
if (fd_fb < 0)
{
printf("can't open /dev/fb0\n");
return -1;
}
if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
{
printf("can't get var\n");
return -1;
}
先打开LCD设备(fb0),获得文件描述符,再通过ioctl获取fb_var_screeninfo信息并保存在var变量,后续只需访问var这个结构体,就可以获得xres(x方向总像素点),yres(y方向总像素点),bits_per_pixel(每个像素点占据的位数)这三个关于fb0的资源
接下来就是根据这些信息计算变量
根据fb_var_screeninfo计算变量
fb_var_screeninfo已保存在var结构体变量中,接着来访问var结构体变量即可
根据xres与bits_per_pixel算出每行像素点所占据的字节数
line_width = var.xres * var.bits_per_pixel / 8;
//line_width 也就是行宽度,也就是x的分辨率*字节,这就算出了每行像素点所占据的字节数
根据bits_per_pixel算出每个像素点所占据的字节数
pixel_width = var.bits_per_pixel / 8;
//pixel_width 也就是像素点的宽度,每一个像素点占多少位/8即为字节
最后根据xres,yres,bits_per_pixel算出全部像素点所占据的字节总和
screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
//screen_size 屏幕所需字节大小
//var.xres * var.yres这个是求面积公式,获得整个屏幕所需多大,与var.bits_per_pixel / 8相乘计算出屏幕所需多少字节
获得屏幕参数和求出了所需的字节大小了,我们接下来就对内存操作,也就是真正意义上操作FrameBuffer
fbmem = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
if (fbmem == (unsigned char *)-1)
{
printf("can't mmap\n");
return -1;
}
/* 清屏: 全部设为黑色 */
memset(fbmem, 0, screen_size);
调用mmap将显存映射在内存中,以可读可写(PROT_READ | PROT_WRITE)及内存回写(MAP_SHARED)的方式映射,从而获得一个指向映射在内存空间的首地址fbmem,后续操作就是在这个首地址的基础上计算各种不同的偏移量,填充颜色值。
到这里就是已经可以控制内存来显示我们所要展示的字符了,接下来看一下描点函数。
描点函数编写
描点函数有3个参数,x坐标,y坐标,像素点颜色值。
void lcd_put_pixel(int x, int y, unsigned int color)
unsigned char *pen_8 = fbmem+y*line_width+x*pixel_width;
unsigned short *pen_16;
unsigned int *pen_32;
unsigned int red, green, blue;
pen_16 = (unsigned short *)pen_8;
pen_32 = (unsigned int *)pen_8;
在此处函数参数x与y表示的是像素点的坐标,而单个像素点所占据的显存大小可能会有不同的情况出现,如1字节表示一个像素点,2字节表示一个像素点,4字节表示一个像素点等,为了更多的兼容不同的情况,因此申请3个指针,pen_8指向的是占据1个字节的像素点空间, pen_16指向的是占据2个字节的像素点空间,pen_32指向的是占据4个字节的像素点空间。
fbmem是系统调用mmap返回的显存首地址,根据fbmem计算填充颜色的内存空间
当像素点占据1个字节空间时
对应描点地址= fbmem+Y * 一行所占据的字节数 + x * 每个像素点所占据的字节数
switch (var.bits_per_pixel)
{
case 8:
{
*pen_8 = color;
break;
}
case 16:
{
/* 565 */
red = (color >> 16) & 0xff;
green = (color >> 8) & 0xff;
blue = (color >> 0) & 0xff;
color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
*pen_16 = color;
break;
}
case 32:
{
*pen_32 = color;
break;
}
default:
{
printf("can't surport %dbpp\n", var.bits_per_pixel);
break;
}
}
}
根据设备fb0实际的bits_per_pixel值,选择对应的pen(pen_8,pen_16,pen_32其中一个),最后把color颜色变量传入选择的pen中,这就完成了对LCD描点的操作。
三、在LCD上使用点阵写字
在LCD上显示英文字母
①找出英文字母在点阵数组中的地址,c所代表的是一个英文字母(ASCII值)。(英文的点阵数组在Linux中已经存在了,所以无需下载)
unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16];
//fontdata_8x16就是存在在Linux中的点阵数组,c是要显示的字符,因为每一个字符需要16个字节来显示,所以用c*16确定字符的位置
②根据获得的英文字母点阵,每一位依次判断,描点,‘1’表示白色,‘0’表示黑色。
根据上图,我们分析下如何利用点阵在LCD上显示一个英文字母,因为有十六行,所以首先要有一个循环16次的大循环,然后每一行里有8位,那么在每一个大循环里也需要一个循环8次的小循环,小循环里的判断单行的描点情况,如果是1,就填充白色,如果是0就填充黑色,如此一来,就可以显示出黑色底,白色轮廓的英文字母
for (i = 0; i < 16; i++)
{
byte = dots[i];
for (b = 7; b >= 0; b--)
{
if (byte & (1<<b))
{
/* show */
lcd_put_pixel(x+7-b, y+i, 0xffffff); /* 白 */
}
else
{
/* hide */
lcd_put_pixel(x+7-b, y+i, 0); /* 黑 */
}
}
}
③调用我们编写的lcd_put_ascii函数
lcd_put_ascii(var.xres/2, var.yres/2, 'A'); /*在屏幕中间显示8*16的字母A*/
④编译c文件show_ascii.c
编译命令:arm-linux-gnueabihf-gcc -o show_ascii show_ascii.c
交叉编译工具链的知识
在linux上gcc编译出来的适用于x86架构,不适用的ARM架构的
所以要想让代码运行在ARM架构,我们需要使用不同的编译工具链
⑤将编译出来的show_ascii传输到开发板,并进入show_ascii的目录下
执行命令:./show_ascii
如果实验成功,我们将看到屏幕中间会显示出一个白色的字母‘A’。
在LCD上显示汉字
与显示英文字母有点不同,因为汉字的点阵我们是需要通过汉字库提取出来,并没有直接提供点阵数组,因此我们程序开头需要打开汉字库文件(HZK16),然后再找到相应的位置,提取出汉字的点阵,最后再按显示英文字母一样显示它,不过这个汉字是16*16的。
① 打开汉字库文件
fd_hzk16 = open("HZK16", O_RDONLY);
② 获取汉字库文件的属性,存在hzk_stat结构体变量中
if(fstat(fd_hzk16, &hzk_stat))
//获取文件的大小,存储在hzk_stat变量中
③使用mmap系统调用
hzkmem = (unsigned char *)mmap(NULL , hzk_stat.st_size,
hzkmem与fbmem类似,也是一个指向映射内存的指针,但是它是指向汉字库,方便后续计算汉字点阵偏移位置用。接下来就是调用汉字点阵库来显示汉字。但是我们需要先了解一下字符的编码格式。
字符的编码格式
不同的字符有不同的编码格式,格式不同,显示出来有可能就是乱码,我们主要了解一下英文,汉字的编码格式。
ASCII码(American Standard Code for Information Interchange,美国信息交换标准代码)是一个7位编码,规定了128个字符(包括数字、字母、标点符号和一些控制字符)及其对应的二进制数值,其中前7位的取值范围为 0~127,最高位为 0。由于它的本质是定长编码,所以不具备国际化能力。
ANSI码(American National Standards Institute,美国国家标准化组织)是对 ASCII 码的扩充,增加了一些控制字符和国际字符,但是ANSI码并没有规定字符集编码方式,因此 ANSI 码对于国际化的支持不够完善。
Unicode编码是一种编码方案,它为世界上所有符号都分配了一个唯一的二进制编码,不仅包括 ASCII 码和 ANSI 码中的字符,还包括了世界上各种语言中存在的符号,甚至包括一些图形符号。Unicode 编码是一个多字节编码,使用不同的字节序列表示不同的符号,并提供了多种编码方式,例如 UTF-8、UTF-16、UTF-32 等。
UTF-8编码是一种可变长的 Unicode 编码方式,使用1~4个字节表示不同的字符,可以节省空间,兼容ASCII码并且支持多语言字符。UTF-8 在网络传输和存储中使用广泛,是当前最常用的 Unicode 编码之一。
GB2312 是一个早期的中文字符集标准,由中国国家标准化委员会于 1980 年发布。它规定了一套单字节字符和 94 个汉字字符,使用了类似 ASCII 码的单字节编码方式,包括了大多数简体中文字符。GB2312 是一个简单、小巧并且有效的编码方法,适合存储和传输中文信息,开创了中文计算机应用的先河。它的编码范围为 0x00~0xFF,其中 0x00~0x7F 与 ASCII 码兼容,0xA1~0xFE 是汉字区。
但是,GB2312 编码方式的缺陷也是显而易见的,它不能覆盖所有的汉字和中文字符,例如繁体汉字、部分方言文字、外来语等无法编码。因此,GB2312 在 1990 年代后期就逐渐被 GBK、GB18030 等更为完善的编码方式所取代。
总体而言,ASCII 码和 ANSI 码是历史编码方式,虽然现在仍然有一些应用,但是由于其不支持国际化等限制,已经逐渐被 Unicode 编码所取代。而 Unicode 编码采用多字节表示一个字符,能够包含世界上各种语言的字符,尤其是亚洲国家的文字和符号,在国际化应用中得到了广泛的应用。而 UTF-8 则是使用最广泛的一种 Unicode 编码,其具有以下特点:兼容 ASCII 码、可变长编码和节省空间,它在现代计算机系统、网络和大规模数据存储等方面具有广泛的应用。、
准确来说呢:ASCII码和ANSI常用于显示英文字符,一个字节对应一个字符,所以有些中文是不能显示的。使用UNICODE码统一编码使用三个字符来显示中文就可以,但是这就会存在浪费。其具有多种编码方式,使用最多的就是UTF-8编码,这能够支持多语言字符,还能变长节省空间,在网络传输数据的时候如果发现丢包的情况下不会导致后面的字符混乱。所以通常使用UTF-8,当然GB2312也是可以显示中文的。
④使用汉字库,调出点阵显示汉字
HZK16 字库是符合GB2312标准的16×16点阵字库HZK16的编码,每个字需要32个字节的点阵来表示,例如我们将要显示的‘中’字,编码是D6D0,难道就是2个字节表示吗?不是说32字节吗?D6D0编码是一个类似于索引码,D6是区码,D0是位码,先要找到D6-A1才是真正区,在D6-A1区里找到D0-A1的真正位置,这才是‘中’字点阵的起始位置(减去A1是为了兼容ascii),每一个区有94个汉字。
unsigned int area = str[0] - 0xA1;
unsigned int where = str[1] - 0xA1;
//兼容ASCII码
unsigned char *dots = hzkmem + (area * 94 + where)*32;
//(area区码算出来+where在区的位置)*32(共有32个字节)
上图是汉字点阵排布的示意图,总共有十六行,因此需要一个循环16次的大循环,考虑到一行有两个字节,我们大循环中加入一个循环2次的循环用于区分是哪个字节,最后判断当前字节的每一位,如果为 ‘1’描白色,如果为‘0’描黑色
for (i = 0; i < 16; i++)
for (j = 0; j < 2; j++)
{
byte = dots[i*2 + j];
for (b = 7; b >=0; b--)
{
if (byte & (1<<b))
{
/* show */
lcd_put_pixel(x+j*8+7-b, y+i, 0xffffff); /* 白 */
}
else
{
/* hide */
lcd_put_pixel(x+j*8+7-b, y+i, 0); /* 黑 */
}
}
}
⑤调用我们编写的lcd_put_chinese函数
printf("chinese code: %02x %02x\n", str[0], str[1]);
lcd_put_chinese(var.xres/2 + 8, var.yres/2, str);
⑥编译c文件show_font.c
编译命令:arm-linux-gnueabihf-gcc -o show_font show_font.c
注:使用此命令HZK16文件必须与show_font.C在同一目录下。
⑦将编译出来的show_font传输到开发板,并进入show_font的目录下
执行命令:./show_font
如果实验成功,我们将看到屏幕中间会显示出一个白色的字母‘A’与汉字‘中’,同时在串口打印信息中看到‘中’对应的编码。
chinese code: d6 d0
四、使用freetype来显示汉字
freetype是什么,为什么要使用freetype
FreeType 是一个开源的字体渲染库,它可以用于将矢量字体文件(如 TrueType、PostScript 和 OpenType 等)渲染成位图或者折线形式,以便在屏幕、打印机等输出设备上显示,同时还能提供一些字体处理的相关函数。
在计算机科学的领域中,字体常用于表示文本和符号,如今也广泛应用于各种应用程序中,例如图形界面、Web 浏览器、手机应用等。字体渲染是字体应用的基础,其中的字体设计、字形排版、字体特效等等都需要借助字体渲染库来实现。
常见的文字渲染实现有两种,一种是在运行时将字体文件转换为位图,以点阵的形式显示在屏幕上,即所谓的“位图字体”;另一种是使用折线算法对字体文件进行转换,以矢量图的形式进行高清渲染,即所谓的“矢量字体”。FreeType 就是一种常见的实现矢量字体渲染的库,它可以根据不同的字体文件进行字形排版、识别和渲染等操作,生成高清的矢量字形图案。
在实际应用中,使用 FreeType 可以适应不同平台和操作系统的要求,处理字符编码、字符大小、字符样式等复杂问题,而且它支持 TrueType、PostScript、OpenType 等多种字体格式,使用起来非常方便。因此,FreeType 被广泛应用于文字处理、操作系统、图形界面等领域。
准确来说,使用点阵显示中文字符的话,放大看会有锯齿的情况出现,并且不支持调节大小,使用freetype可以自定义调节字体的大小。
交叉编译freetype,并安装
文件去官网下载,然后通过FileZilla
①解压freetype源文件
tar xjf freetype-2.4.10.tar.bz2
②进入解压后的freetype-2.4.10目录
cd freetype-2.4.10
③配置freetype-2.4.10
./configure --host=arm-linux-gnueabihf --prefix=/home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/
④建个目录,避免后面安装出错提示缺少这个internal目录
mkdir /home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/include/freetype2/freetype/internal -p
⑤编译
make
⑥安装
make install
最后移动freetype头文件,避免以后编译总是需要指定头文件路径
mv /home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/include/freetype2/freetype /home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/include/
由于100ask开发板已经有freetype相关的库和头文件,因此不需要移植,如果开发板没有freetype库和头文件就需要按以下方法移植
/home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/include/* 复制到开发板的头文件目录中
/home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/lib/so 复制到开发板的库文件目录中
矢量字体引入
点阵显示英文字母,汉字时,大小固定,如果放大会有锯齿出现,为了解决这个问题,引用矢量字体。矢量字体形成分三步,若干的关键点,数学曲线(贝塞尔曲线),填充颜色组合而成。
①假设A字母的关键点如图中的黄色圈圈,确定关键点。
②用数学曲线将关键点都连接起来,成为封闭的曲线。
③最后把封闭空间填满颜色,就显示出一个A字母。
如果需要放大或者缩小字体,关键点的相对位置是不变的,跟进放大比例放大或缩小,但是相对位置不变,好像分数中的1/2 和 2/4,比例是不变的,但是值却大了,类似这个味道。
Freetype理论介绍
开源的Freetype字体引擎库它提供统一的接口来访问多种字体格式文件,从而实现矢量字体显示。我们只需要移植这个字体引擎,调用对应的API接口,提供字体关键点,就可以让freetype库帮我们实现闭合曲线,填充颜色,达到显示矢量字体的目的。
关键点(glyph)存在字体文件中,Windows使用的字体文件在FONTS目录下,扩展名为TTF的都是矢量字库,本次使用实验使用的是新宋字体simsun.ttc。
说白了就是我们要提供字体的关键点,freetype库会帮我们实现闭合曲线那些操作。
字体文件结构如上图
Charmaps表示字符映射表(通过这个字符映射找到字符的位置,获取它的关键点),字体文件可能支持哪一些编码,GBK,UNICODE,BIG5还是别的编码,如果字体文件支持该编码,跟进编码,通过charmap,找到对应的glyph,一般而言都支持UNICODE码。
因此一个文字的显示过程大概如下
①给定一个文字吗‘A’(0x41),‘中’(GBK,UNICODE ,BIG5)可以确定它的编码值;
②跟进编码值,从枝头文件中通过charmap找到对应的关键点(glyph);
③设置字体大;
④用某些函数把关键点(glyph)缩放为我们设置的字体大小;
⑤转换为位图点阵
⑥在LCD上显示出来
如上图,参照step1,step2,step3里的内容,可以学习如何使用freetype库,大致总结下,为如下步骤。
①初始化:FT_InitFreetype
②加载(打开)字体Face:FT_New_Face
③设置字体大小:FT_Set_Char_Sizes 或 FT_Set_Pixel_Sizes
④选择charmap:FT_Select_Charmap
⑤根据编码值charcode找到glyph : glyph_index = FT_Get_Char_Index(face,charcode)
⑥根据glyph_index取出glyph:FT_Load_Glyph(face,glyph_index)
⑦转为位图:FT_Render_Glyph
⑧移动或旋转:FT_Set_Transform
在LCD上显示字符
我们可以参考上图位置的c程序,编写程序。
①初始化freetype库
error = FT_Init_FreeType( &library );
②用freetype库中的FT_New_Face函数创建一个face字体文件对象,保存在&face中
error = FT_New_Face( library, argv[1], 0, &face );
③提取face对象中的glyph,即关键点集
slot = face->glyph;
④设置像素点大小,24*24
FT_Set_Pixel_Sizes(face, 24, 0);
⑤确定坐标
目前我们前面所用的都是LCD的坐标系对应的x与y坐标,然后在freetype上却是使用的笛卡尔坐标系,因此我们还需要转换x与y坐标。
我们将要显示的是‘繁’字,根据上图可知,先计算在lcd坐标系的情况下‘繁’字的左下角的x坐标与y坐标,因为在笛卡尔坐标中左下角为字符的原点,‘A’是的左上角为整个屏幕的中心点,即(xres/2,yres/2)。
lcd_x = var.xres/2 + 8 + 16;
lcd_y = var.yres/2 + 16
则笛卡尔座标系:x = lcd_x = var.xres/2 + 8 + 16 ; y = var.yres - lcd_y = var.yres/2 – 16
单位是1/64像素,所以需要乘以64
pen.x = (var.xres/2 + 8 + 16) * 64;
pen.y = (var.yres/2 - 16) * 64;
FT_Set_Transform( face, 0, &pen);
⑥找到glyph的位置,然后取出,并转换为位图
error = FT_Load_Char( face, chinese_str[0], FT_LOAD_RENDER );
if (error)
{
printf("FT_Load_Char error\n");
return -1;
}
FT_Load_Char函数调用替代了上图这3步。最后把转换出来的位图打印出来,也是参考example1.c编写
draw_bitmap( &slot->bitmap,slot->bitmap_left,var.yres - slot->bitmap_top);
修改上图3处位置
Width宽度:因为在LCD上显示,宽度自然就是x方向的像素点数,var.xres;
Height高度:因为在LCD上显示,高度自然就是y方向的像素点数,var.yres;
用点阵实验中的的描点函数lcd_put_pixel替代image数组:lcd_put_pixel(i, j, bitmap->buffer[q * bitmap->width + p]);
⑥编译C程序文件freetype_show_font.c
编译命令:
arm-linux-gnueabihf-gcc -finput-charset=GBK -fexec-charset=GBK -o freetype_show_font freetype_show_font.c -lfreetype -lm
⑦将编译好的freetype_show_font的文件与simsun.ttc字体文件拷贝至开发板,simsun.ttc字体文件放在freetype_show_font执行文件的上一层目录下,执行以下命令。
执行命令:
./freetype_show_font …/simsun.ttc
如果实验成功,我们将看到屏幕中间会比之前实验多出一个蓝色的‘繁’字。
在LCD上令矢量字体旋转某个角度
在实现显示一个矢量字体后,我们可以添加让该字旋转某个角度的功能。我们根据输入的第二个参数,判断其旋转角度,主要代码还是参照example1.c
根据上图,增加旋转角度功能,旋转的角度由执行命令的第二个参数指定。
angle = ( 1.0 * strtoul(argv[2], NULL, 0) / 360 ) * 3.14159 * 2;
matrix.xx = (FT_Fixed)( cos( angle ) * 0x10000L );
matrix.xy = (FT_Fixed)(-sin( angle ) * 0x10000L );
matrix.yx = (FT_Fixed)( sin( angle ) * 0x10000L );
matrix.yy = (FT_Fixed)( cos( angle ) * 0x10000L );
FT_Set_Transform( face, &matrix, &pen);
最后编译,在开发板上运行
编译命令如下:
编译命令:
arm-linux-gnueabihf-gcc -finput-charset=GBK -fexec-charset=GBK -o freetype_show_font_angle freetype_show_font_angle.c -lfreetype -lm
编译出的文件名为freetype_show_font_angle,将文件拷贝至开发板
在含有该文件的目录下执行以下命令,以下命令正确执行前提是执行文件freetype_show_font在此目录,而且字体文件simsun.ttc,在上一级目录:
执行命令:
./freetype_show_font_angle …/simsun.ttc 90
如果实验成功,我们将看到屏幕中间的蓝色‘繁’字,旋转了90度。
在LCD上使用freetype显示一行文字
在LCD上指定一个左上角坐标(x, y),把一行文字显示出来。下图中,文字的外框用虚线表示,外框的左上角坐标就是(x, y)。
上面我们已经了解了LCD使用的是笛卡尔坐标系。我们需要注意到每个字符的大小可能不同,在使用FT_Set_Pixel_Sizes函数设置字体大小时,这只是“期望值”。比如“百问网www.100ask.net”,如果把“.”显示得跟其他汉字一样大,不好看。所以在显示一行文字时,后面文字的位置会受到前面文字的影响。幸好,freetype帮我们考虑到了这些影响。对于freetype字体的尺寸(freetype Metrics),需要参考下图这个文档。
在显示一行文字时,这些文字会基于同一个基线来绘制位图:baseline。
在baseline上,每一个字符都有它的原点(origin),比如上图中baseline左边的黑色圆点就是字母“g”的原点。当前origin加上advance就可以得到下一个字符的origin,比如上图中baseline右边的黑色圆点。在显示一行中多个文件字时,后一个文字的原点依赖于前一个文字的原点及advance。
字符的位图是有可能越过baseline的,比如上图中字母“g”在baseline下方还有图像。
上图中红色方框内就是字母“g”所点据的位图,它的四个角落不一定与原点重合。
上图中那些xMin、xMax、yMin、yMax如何获得?可以使用FT_Glyph_Get_CBox函数获得一个字体的这些参数,将会保存在一个FT_BBox结构体中,以后想计算一行文字的外框时要用到这些信息:
在指定位置显示一行文字
要显示一行文字时,每一个字符都有自己外框:xMin、xMax、yMin、yMax。把这些字符的xMin、yMin中的最小值取出来,把这些字符的xMax、yMax中的最大值取出来,就可以确定这行文字的外框了。要想在指定位置(x, y)显示一行文字,步骤如下图所示:
① 先指定第1个字符的原点pen坐标为(0, 0),计算出它的外框
② 再计算右边字符的原点,也计算出它的外框
把所有字符都处理完后就可以得到一行文字的整体外框:假设外框左上角坐标为(x’, y’)。
③ 想在(x, y)处显示这行文字,调整一下pen坐标即可
怎么调整?
pen为(0, 0)时对应左上角(x’, y’);
那么左上角为(x, y)时就可以算出pen为(x-x’, y-y’)。
freetype的几个重要数据结构
要想形象地理解程序,需要先介绍一下freetype中几个数据结构:
FT_Library
对应freetype库,使用freetype之前要先调用以下代码:
FT_Library library; /* 对应freetype库 */
error = FT_Init_FreeType( &library ); /* 初始化freetype库 */
FT_Face
它对应一个矢量字体文件,在源码中使用FT_New_Face函数打开字体文件后,就可以得到一个face。
为什么称之为face?估计是文字都是写在二维平面上的吧,正对着人脸?不用管原因了,总之认为它对应一个字体文件就可以。
代码如下:
error = FT_New_Face(library, font_file, 0, &face ); /* 加载字体文件 */
FT_GlyphSlot
插槽?用来保存字符的处理结果:比如转换后的glyph、位图,如下图:
一个face中有很多字符,生成一个字符的点阵位图时,位图保存在哪里?保存在插槽中:face->glyph。生成第1个字符位图时,它保存在face->glyph中;生成第2个字符位图时,也会保存在face->glyph中,会覆盖第1个字符的位图。
代码如下:
FT_GlyphSlot slot = face->glyph; /* 插槽: 字体的处理结果保存在这里 */
FT_Glyph
字体文件中保存有字符的原始关键点信息,使用freetype的函数可以放大、缩小、旋转,这些新的关键点保存在插槽中(注意:位图也是保存在插槽中)。
新的关键点使用FT_Glyph来表示,可以使用这样的代码从slot中获得glyph:
error = FT_Get_Glyph(slot , &glyph);
FT_BBox
FT_BBox结构体定义如下,它表示一个字符的外框,即新glyph的外框:
可以使用以下代码从glyph中获得这些信息:
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &bbox );
上述流程如下:
计算一行文字的外框
前面提到过,一行文字中:后一个字符的原点=前一个字符的原点+advance。
所以要计算一行文字的外框,需要按照排列顺序处理其中的每一个字符。
代码如下,注释写得很清楚了
int compute_string_bbox(FT_Face face, wchar_t *wstr, FT_BBox *abbox)
{
int i;
int error;
FT_BBox bbox;
FT_BBox glyph_bbox;
FT_Vector pen;
FT_Glyph glyph;
FT_GlyphSlot slot = face->glyph;
/* 初始化 */
bbox.xMin = bbox.yMin = 32000;
bbox.xMax = bbox.yMax = -32000;
/* 指定原点为(0, 0) */
pen.x = 0;
pen.y = 0;
/* 计算每个字符的bounding box */
/* 先translate, 再load char, 就可以得到它的外框了 */
for (i = 0; i < wcslen(wstr); i++)
{
/* 转换:transformation */
FT_Set_Transform(face, 0, &pen);
/* 加载位图: load glyph image into the slot (erase previous one) */
error = FT_Load_Char(face, wstr[i], FT_LOAD_RENDER);
if (error)
{
printf("FT_Load_Char error\n");
return -1;
}
/* 取出glyph */
error = FT_Get_Glyph(face->glyph, &glyph);
if (error)
{
printf("FT_Get_Glyph error!\n");
return -1;
}
/* 从glyph得到外框: bbox */
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &glyph_bbox);
/* 更新外框 */
if ( glyph_bbox.xMin < bbox.xMin )
bbox.xMin = glyph_bbox.xMin;
if ( glyph_bbox.yMin < bbox.yMin )
bbox.yMin = glyph_bbox.yMin;
if ( glyph_bbox.xMax > bbox.xMax )
bbox.xMax = glyph_bbox.xMax;
if ( glyph_bbox.yMax > bbox.yMax )
bbox.yMax = glyph_bbox.yMax;
/* 计算下一个字符的原点: increment pen position */
pen.x += slot->advance.x;
pen.y += slot->advance.y;
}
/* return string bbox */
*abbox = bbox;
}
调整原点并绘制
int display_string(FT_Face face, wchar_t *wstr, int lcd_x, int lcd_y)
{
int i;
int error;
FT_BBox bbox;
FT_Vector pen;
FT_Glyph glyph;
FT_GlyphSlot slot = face->glyph;
/* 把LCD坐标转换为笛卡尔坐标 */
int x = lcd_x;
int y = var.yres - lcd_y;
/* 计算外框 */
compute_string_bbox(face, wstr, &bbox);
/* 反推原点 */
pen.x = (x - bbox.xMin) * 64; /* 单位: 1/64像素 */
pen.y = (y - bbox.yMax) * 64; /* 单位: 1/64像素 */
/* 处理每个字符 */
for (i = 0; i < wcslen(wstr); i++)
{
/* 转换:transformation */
FT_Set_Transform(face, 0, &pen);
/* 加载位图: load glyph image into the slot (erase previous one) */
error = FT_Load_Char(face, wstr[i], FT_LOAD_RENDER);
if (error)
{
printf("FT_Load_Char error\n");
return -1;
}
/* 在LCD上绘制: 使用LCD坐标 */
draw_bitmap( &slot->bitmap,
slot->bitmap_left,
var.yres - slot->bitmap_top);
/* 计算下一个字符的原点: increment pen position */
pen.x += slot->advance.x;
pen.y += slot->advance.y;
}
return 0;
}
编译命令(如果你使用的交叉编译链前缀不是arm-buildroot-linux-gnueabihf,请自行修改命令):
$ arm-buildroot-linux-gnueabihf-gcc -o show_line show_line.c -lfreetype
将编译好的show_line文件与simsun.ttc字体文件拷贝至开发板,这2个文件放在同一个目录下,然后执行以下命令(其中的3个数字分别表示LCD的X坐标、Y坐标、字体大小):
[root@board:~]# ./show_line ./simsun.ttc 10 200 80
如果实验成功,可以在LCD上看到一行文字“百问网www.100ask.net”。
总结
本文主要记录了FrameBuffer应用编程,介绍了其概念,使用LCD显示字符,中文,一行中文,还介绍了字符的不同的编码格式。最后使用freetype库显示单个矢量文字和显示一行矢量文字。