1.3.3 汉字区位码
我们从网上搜到HZK16这个文件,它是常用汉字的16*16点阵字库。HZK16里每个汉字使用32字节来描述,如下图所示:
跟ASCII字库一样,每个字节中每一位用来表示一个像素,位值等于1时表示对应像素被点亮,位值等于0时表示对应像素被熄灭。
HZK16中是以GB2312编码值来查找点阵的,以“中”字为例,它的编码值是“0xd6 0xd0”,其中的0xd6表示“区码”,表示在哪一个区:第“0xd6 - 0xa1”区;其中的0xd0表示“位码”,表示它是这个区里的哪一个字符:第“0xd0 - 0xa1”个。每一个区有94个汉字。区位码从0xa1而不是从0开始,是为了兼容ASCII码。
所以,我们要显示的“中”字,它的GB2312编码是d6d0,它是HZK16里第“(0xd6-0xa1)*94+(0xd0-0xa1)”个字符。
1.3.4 汉字点阵显示实验
打开汉字库文件
4787 fd_hzk16 = open("HZK16", O_RDONLY); 4788 if (fd_hzk16 < 0) 4789 { 4790 printf("can't open HZK16\n"); 4791 return -1; 4792 } 4793 if(fstat(fd_hzk16, &hzk_stat)) 4794 { 4795 printf("can't get fstat\n"); 4796 return -1; 4797 } 4798 hzkmem = (unsigned char *)mmap(NULL , hzk_stat.st_size, PROT_READ, MAP_SHARED, fd_hzk16, 0); 4799 if (hzkmem == (unsigned char *)-1) 4800 { 4801 printf("can't mmap for hzk16\n"); 4802 return -1; 4803 }
第4787行打开当前目录的字库文件:HZK16。
第4793行获得文件的状态信息,里面含有文件长度,这在后面的mmap中用到。
第4798行使用mmap映射文件,以后就可以像访问内存一样读取文件内容;mmap的返回结果保存在hzkmem中,它将作为字库的基地址。
2.编写显示汉字的函数
核心函数是void lcd_put_chinese(int x, int y, unsigned char *str),它在LCD的(x,y)位置处显示汉字字符str,str[0]中保存区码、str[1]中保存位码。
代码如下图所示:
代码分解如下:
第4734行确定该汉字属于哪个区;第4735行确实它是该区中哪一个汉字。
第4736行确实它的字库地址:每个区中有94个汉字,每个汉字在字库中占据32字节。
需要根据下面的图来理解第4740行开始的循环:
上图是汉字点阵排布的示意图,总共有十六行,因此需要一个循环16次的大循环(第4740行)。
考虑到一行有两个字节,在大循环中加入一个2次的循环用于区分是哪个字节(第4741行)。
最后使用第3个循环来处理一个字节中的8位(第4744行)。对于每一位,它等于1时对应的像素被设置为白色,它等于0时对应的像素被设置为黑色。需要注意的是根据x、y、i、j、b来计算像素坐标。
3.使用lcd_put_chinese函数
程序文件:show_font.c
4762 unsigned char str[] = "中"; …… 4810 printf("chinese code: %02x %02x\n", str[0], str[1]); 4811 lcd_put_chinese(var.xres/2 + 8, var.yres/2, str);
4.编译程序
编译命令:
arm-buildroot-linux-gnueabihf-gcc -o show_chinese show_chinese.c
注意:不同的板子,编译工具的前缀可能不一样。
注意:使用上述命令时show_chinese.c的编码格式必须是ANSI(GB2312),否则编译时需要指定“-fexec-charset=GB2312”。
5.上机实验
把show_chinese程序放到板子上,执行命令:
./show_chinese
如果实验成功,我们将看到屏幕中间会显示出一个白色的字母“A”和“中”。
1.4 交叉编译程序:以freetype为例(这部分很有用)
使用buildroot来给ARM板编译程序、编译库会很简单,以后系统讲解buildroot时再使用buildroot。现在我们还是手工交叉编译freetype,这种方法在编译、安装一些小程序时很有用。
以后肯定会遇到编译运行一些网络下载的库文件,这时候如何手动编译这些库给自己需要的程序使用,这就很关键。
1.4.1 程序运行的一些基础知识
编译程序时去哪找头文件?
系统目录:就是交叉编译工具链里的某个include目录;也可以自己指定:编译时用 “ -I dir ”选项指定。
链接时去哪找库文件?
系统目录:就是交叉编译工具链里的某个lib目录;也可以自己指定:链接时用 “ -L dir ”选项指定。
运行时去哪找库文件?
系统目录:就是板子上的/lib、/usr/lib目录;也可以自己指定:运行程序用环境变量LD_LIBRARY_PATH指定。
1.4.2 常见错误的解决方法
头文件问题
编译时找不到头文件。在程序中这样包含头文件:#include <xxx.h> 对于尖括号里的头文件,去哪里找它?
系统目录:就是交叉编译工具链里的某个include目录;也可以自己指定:编译时用 “ -I dir ”选项指定。
怎么确定“系统目录”?
执行下面命令确定目录:
echo 'main(){}'| arm-buildroot-linux-gnueabihf-gcc -E -v -
它会列出头文件目录、库目录(LIBRARY_PATH)。你需要在头文件目录中确定有没有这个文件,或是自己指定头文件目录。
库文件问题
链接程序时如果有这样的提示:undefined reference toxxx’`,它表示xxx函数未定义。
那么解决方法有2:
① 去写出这个函数
② 或是使用库函数,那需要在链接时指定库
怎么指定库?想链接libabc.so,那链接时加上:-labc。库在哪里?
① 系统目录:就是交叉编译工具链里的某个lib目录
② 也可以自己指定:链接时用 “ -L dir ”选项指定
怎么确定“系统目录”?执行下面命令确定目录:
echo 'main(){}'| arm-buildroot-linux-gnueabihf-gcc -E -v –
它会列出头文件目录、库目录(LIBRARY_PATH),你编译出库文件时,可以把它放入系统库目录。
运行问题
运行程序时找不到库:
error while loading shared libraries: libxxx.so: cannot open shared object file: No such file or directory
找不到库,库在哪?
① 系统目录:就是板子上的/lib、/usr/lib目录
② 也可以自己指定:运行程序用环境变量LD_LIBRARY_PATH指定,执行以下的命令:
export LD_LIBRARY_PATH=/xxx_dir ./test
或
LD_LIBRARY_PATH=/xxx_dir ./test
1.4.3 交叉编译程序的万能命令
如果交叉编辑工具链的前缀是arm-buildroot-linux-gnueabihf-,比如arm-buildroot-linux-gnueabihf-gcc,交叉编译开源软件时,如果它里面有configure,万能命令如下:
./configure --host=arm-buildroot-linux-gnueabihf --prefix=$PWD/tmp make make install
就可以在当前目录的tmp目录下看见bin, lib, include等目录,里面存有可执行程序、库、头文件。
把头文件、库文件放到工具链目录里
如果你编译的是一个库,请把得到的头文件、库文件放入工具链的include、lib目录里。别的程序要使用这些头文件、库时,会很方便。
工具链里可能有多个include、lib目录,放到哪里去?
执行下面命令来确定目录:
echo 'main(){}'| arm-buildroot-linux-gnueabihf-gcc -E -v –
它会列出头文件目录、库目录(LIBRARY_PATH)。
把库文件放到板子上的/lib或/usr/lib目录里
程序在板子上运行时,需要用到板子上/lib或/usr/lib下的库文件;程序运行时不需要头文件。
1.4.4 给IMX6ULL交叉编译freetype
使用GIT下载所有源码后,本节源码位于如下目录:
01_all_series_quickstart\04_嵌入式Linux应用开发基础知识\source\10_freetype\ freetype-2.10.2.tar.xz libpng-1.6.37.tar.xz zlib-1.2.11.tar.gz
freetype依赖于libpng,libpng又依赖于zlib,所以我们应该:先编译安装zlib,再编译安装libpng,最后编译安装freetype。
但是,有些工具链里有zlib, 那就不用编译安装zlib,比如STM32MP157。
对于IMX6ULL,由于版本原因,使用过两套工具链:
比较精简的
export ARCH=arm export CROSS_COMPILE=arm-linux-gnueabihf- export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin
它里面没有zlib,需要参考下面的文档先编译安装zlib。
比较完善的(基于buildroot)
export ARCH=arm export CROSS_COMPILE=arm-buildroot-linux-gnueabihf- export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin
它里面有zlib,跟着视频操作即可。
本节文档以IMX6ULL开发板中arm-linux-gnueabihf-gcc工具链为例,对于其他开发板:工具链可能不一样,请灵活变通。
确定头文件、库文件在工具链中的目录
先设置交叉编译工具链:
export ARCH=arm export CROSS_COMPILE=arm-linux-gnueabihf- export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin
以IMX6ULL开发板为例,它的工具链是arm-linux-gnueabihf-gcc,可以执行以下命令:
echo 'main(){}'| arm-linux-gnueabihf-gcc -E -v -
可以确定头文件的系统目录为:
/home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin/../arm-linux-gnueabihf/libc/usr/include
库文件的系统目录为:
/home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin/../arm-linux-gnueabihf/libc/usr/lib/
交叉编译、安装zlib
libpng依赖于zlib,所以需要先编译、安装zlib。命令如下:
book@PC$ tar xzf zlib-1.2.11.tar.gz book@PC$ cd zlib-1.2.11 book@PC$ export CC=arm-linux-gnueabihf-gcc book@PC$ ./configure --prefix=$PWD/tmp book@PC$ make book@PC$ make install book@PC$ cd tmp book@PC$ cp include/* -rf /home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin/../arm-linux-gnueabihf/libc/usr/include book@PC$ cp lib/* -rfd /home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin/../arm-linux-gnueabihf/libc/usr/lib/
交叉编译、安装libpng
freetype依赖于libpng,所以需要先编译、安装libpng。命令如下:
book@PC$ tar xJf libpng-1.6.37.tar.xz book@PC$ cd libpng-1.6.37 book@PC$ ./configure --host=arm-linux-gnueabihf --prefix=$PWD/tmp book@PC$ make book@PC$ make install book@PC$ cd tmp book@PC$ cp include/* -rf /home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin/../arm-linux-gnueabihf/libc/usr/include book@PC$ cp lib/* -rfd /home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin/../arm-linux-gnueabihf/libc/usr/lib/
交叉编译、安装freetype
命令如下:
book@PC$ tar xJf freetype-2.10.2.tar.xz book@PC$ cd freetype-2.10.2 book@PC$ ./configure --host=arm-linux-gnueabihf --prefix=$PWD/tmp book@PC$ make book@PC$ make install book@PC$ cd tmp book@PC$ cp include/* -rf /home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin/../arm-linux-gnueabihf/libc/usr/include book@PC$ cp lib/* -rfd /home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin/../arm-linux-gnueabihf/libc/usr/lib/
1.5 使用freetype显示单个文字
使用GIT下载所有源码后,本节源码位于如下目录:
01_all_series_quickstart\04_嵌入式Linux应用开发基础知识\source\10_freetype\ 01_wchar\test_wchar.c 02_freetype_show_font\freetype_show_font.c 03_freetype_show_font_angle\freetype_show_font_angle.c
1.5.1 矢量字体引入
使用点阵字库显示英文字母、汉字时,大小固定,如果放大缩小则会模糊甚至有锯齿出现,为了解决这个问题,引用矢量字体。
矢量字体形成分三步:
① 确定关键点,
② 使用数学曲线(贝塞尔曲线)连接头键点,
③ 填充闭合区线内部空间。
什么是关键点?以字母“A”为例,它的的关键点如下图中的黄色所示。
再用数学曲线(比如贝塞尔曲线)将关键点都连接起来,得到一系列的封闭的曲线,如下图所示:
最后把封闭空间填满颜色,就显示出一个A字母,如下图所示:
如果需要放大或者缩小字体,关键点的相对位置是不变的,只要数学曲线平滑,字体就不会变形。
1.5.2 Freetype介绍
前面我们交叉编译了freetype这个程序。现在介绍一下我们编译这个程序是干什么?
Freetype是开源的字体引擎库,它提供统一的接口来访问多种字体格式文件,从而实现矢量字体显示。我们只需要移植这个字体引擎,调用对应的API接口,提供字体文件,就可以让freetype库帮我们取出关键点、实现闭合曲线,填充颜色,达到显示矢量字体的目的。
关键点(glyph)存在字体文件中,Windows使用的字体文件在c:\Windows\Fonts目录下,扩展名为TTF的都是矢量字库,本次使用实验使用的是新宋字体simsun.ttc。
给定一个字符,怎么在字体文件中找到它的关键点?
首先要确定该字符的编码值:比如ASCII码、GB2312码、UNICODE码。如果字体文件支持某种编码格式(charset),就可以使用这类编码值去找到该字符的关键点(glyph)。有些字体文件支持多种编码格式(charset),这在文件中被称为charmaps(注意:这个单词是复数,意味着可能支持多种charset)。
以simsun.ttc为值,该字体文件的格如下:头部含有charmaps,可以使用某种编码值去charmaps中找到它对应的关键点。下图中的“A、B、中、国、韦”等只是glyph的示意图,表示关键点。
Charmaps表示字符映射表,字体文件可能支持哪一些编码,GB2312、UNICODE、BIG5或其他。如果字体文件支持该编码,使用编码值通过charmap就可以找到对应的glyph,一般而言都支持UNICODE码。
有了以上基础,一个文字的显示过程可以概括如下:
① 给定一个字符可以确定它的编码值(ASCII、UNICODE、GB2312);
② 设置字体大小;
③ 根据编码值,从文件头部中通过charmap找到对应的关键点(glyph),它会根据字体大小调整关键点;
④ 把关键点转换为位图点阵;
⑤ 在LCD上显示出来
从这里可以下载到“freetype-doc-2.10.2.tar.xz”,下图中的文件就是官方文档:
参照上图中step1,step2,step3里的内容,可以学习如何使用freetype库,总结出下列步骤:
① 初始化:FT_InitFreetype ② 加载(打开)字体Face:FT_New_Face ③ 设置字体大小:FT_Set_Char_Sizes 或 FT_Set_Pixel_Sizes ④ 选择charmap:FT_Select_Charmap ⑤ 根据编码值charcode找到glyph_index:glyph_index = FT_Get_Char_Index(face,charcode) ⑥ 根据glyph_index取出glyph:FT_Load_Glyph(face,glyph_index) ⑦ 转为位图:FT_Render_Glyph ⑧ 移动或旋转:FT_Set_Transform ⑨ 最后显示出来。
上面的⑤⑥⑦可以使用一个函数代替:FT_Load_Char(face, charcode, FT_LOAD_RENDER),它就可以得到位图。