1.5.3 在LCD上显示一个矢量字体
使用wchar_t获得字符的UNICODE值
要显示一个字符,首先要确定它的编码值。常用的是UNICODE编码,在程序里使用这样的语句定义字符串时,str中保存的要么GB2312编码值,要么是UTF-8格式的编码值,即使编译时使用“-fexec-charset=UTF-8”,str中保存的也不是直接能使用的UNICODE值:char *str = “中”;
如果想在代码中能直接使用UNICODE值,需要使用wchar_t,宽字符,示例代码如下:
01 #include <stdio.h> 02 #include <string.h> 03 #include <wchar.h> 04 05 int main( int argc, char** argv) 06 { 07 wchar_t *chinese_str = L"中gif"; 08 unsigned int *p = (wchar_t *)chinese_str; 09 int i; 10 11 printf("sizeof(wchar_t) = %d, str's Uniocde: \n", (int)sizeof(wchar_t)); 12 for (i = 0; i < wcslen(chinese_str); i++) 13 { 14 printf("0x%x ", p[i]); 15 } 16 printf("\n"); 17 18 return 0; 19 } 20 }
以UTF-8格式保存test_wchar.c,编译、测试命令如下:
book@book-virtual-machine:~/10_freetype/01_wchar$ gcc -o test_wchar test_wchar.c book@book-virtual-machine:~/10_freetype/01_wchar$ ./test_wchar sizeof(wchar_t) = 4, str's Uniocde: 0x4e2d 0x67 0x69 0x66
每个wchar_t占据4字节,可执行程序里wchar_t中保存的就是字符的UNICODE值。
注意:如果test_wchar.c是以ANSI(GB2312)格式保存,那么需要使用以下命令来编译:
gcc -finput-charset=GB2312 -fexec-charset=UTF-8 -o test_wchar test_wchar.c
使用freetype得到位图
参考“freetype-doc-2.10.2\freetype-2.10.2\docs\tutorial\image.c”,使用freetype显示一个字符并不难。使用GIT下载所有源码后,本节源码位于如下目录:
01_all_series_quickstart\ 04_嵌入式Linux应用开发基础知识\source\10_freetype\ 02_freetype_show_font\freetype_show_font.c
要使用freetype得到一个字符的位图,只需要4个步骤,代码先贴出来再分析:
① 初始化freetype库
158 error = FT_Init_FreeType( &library ); /* initialize library */
② 加载字体文件,保存在&face中:
161 error = FT_New_Face( library, argv[1], 0, &face ); /* create face object */ 162 /* error handling omitted */ 163 slot = face->glyph;
第163行是从face中获得FT_GlyphSlot,后面的代码中文字的位图就是保存在FT_GlyphSlot里。
③ 设置字体大小
165 FT_Set_Pixel_Sizes(face, font_size, 0);
④ 根据编码值得到位图
使用FT_Load_Char函数,就可以实现这3个功能:
a. 根据编码值获得glyph_index:FT_Get_Char_Index
b. 根据glyph_idex取出glyph:FT_Load_Glyph
c. 渲染出位图:FT_Render_Glyph
代码如下:
175 /* load glyph image into the slot (erase previous one) */ 176 error = FT_Load_Char( face, chinese_str[0], FT_LOAD_RENDER );
执行FT_Load_Char之后,字符的位图被存在slot->bitmap里,即face->glyph->bitmap。
3. 在屏幕上显示位图
位图里的数据格式是怎样的?参考example1.c的代码,可以得到下面这个图:
要在屏幕上显示出这些位图,并不复杂:
183 draw_bitmap( &slot->bitmap, 184 var.xres/2, 185 var.yres/2);
draw_bitmap函数代码如下,由于位图中每一个像素用一个字节来表示,在0x00RRGGBB的颜色格式中它只能表示蓝色,所以在LCD上显示出来的文字是蓝色的:
4. 编译
编译命令(如果你使用的交叉编译链前缀不是arm-buildroot-linux-gnueabihf,请自行修改命令):
$ arm-buildroot-linux-gnueabihf-gcc -o freetype_show_font freetype_show_font.c -lfreetype
它会提示如下错误:
freetype_show_font.c:12:10: fatal error: ft2build.h: No such file or directory #include <ft2build.h> ^~~~~~~~~~~~ compilation terminated.
我们不是已经编译过freetype并且把头文件复制进工具链里了吗?怎么还有这个错误?
我们编译出freetype后,得到的ft2build.h是位于freetype2目录里,我们把整个freetype2目录复制进了工具链里。
但是包括头文件时,用的是“#include <ft2build.h>”,要么改成:
#include <freetype2/ft2build.h> 要么把工具链里incldue/freetype2/*.h 复制到上一级目录,我们使用这种方法:跟freetype文档保持一致。执行以下命令: book@PC$ cd /home/book/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot/usr/include book@PC$ mv freetype2/* ./
然后再次执行以下命令:
$ arm-buildroot-linux-gnueabihf-gcc -o freetype_show_font freetype_show_font.c -lfreetype
上机
将编译好的freetype_show_font文件与simsun.ttc字体文件拷贝至开发板,这2个文件放在同一个目录下,然后执行以下命令。
[root@board:~]# ./freetype_show_font ./simsun.ttc 或 [root@board:~]# ./freetype_show_font ./simsun.ttc 300
如果实验成功,我们将在屏幕中间看到一个蓝色的“繁”字。
1.5.4 LCD上令矢量字体旋转某个角度
使用GIT下载所有源码后,本节源码位于如下目录:
01_all_series_quickstart\ 04_嵌入式Linux应用开发基础知识\source\10_freetype\ 03_freetype_show_font_angle\freetype_show_font_angle.c
在实现显示一个矢量字体后,我们可以添加让该字旋转某个角度的功能,主要代码还是参照example1.c。
在实现显示一个矢量字体后,我们可以添加让该字旋转某个角度的功能,主要代码还是参照example1.c。
关键代码
① 定义2个变量:角度、矩阵,如下:
② 设置角度值:
③ 设置矩阵、交形、加载位图:
2. 编译
编译命令(如果你使用的交叉编译链前缀不是arm-buildroot-linux-gnueabihf,请自行修改命令):
$ arm-buildroot-linux-gnueabihf-gcc -o freetype_show_font_angle freetype_show_font_angle.c -lfreetype -lm
上机
将编译好的freetype_show_font_angle文件与simsun.ttc字体文件拷贝至开发板,这2个文件放在同一个目录下,然后执行以下命令。
[root@board:~]# ./freetype_show_font_angle ./simsun.ttc 90 200
如果实验成功,我们将在屏幕中间看到一个蓝色的、旋转了90度的“繁”字。
1.6 使用freetype显示一行字
使用GIT下载所有源码后,本节源码位于如下目录:
01_all_series_quickstart\ 04_嵌入式Linux应用开发基础知识\source\10_freetype\ 04_show_line\show_line.c
本节的目的:在LCD上指定一个左上角坐标(x, y),把一行文字显示出来。下图中,文字的外框用虚线表示,外框的左上角坐标就是(x, y)。
1.6.1 笛卡尔坐标系
在LCD的坐标系中,原点在屏幕的左上角。对于笛卡尔坐标系,原点在左下角。freetype使用笛卡尔坐标系,在显示时需要转换为LCD坐标系。
从下图可知,X方向坐标值是一样的。
在Y方向坐标值需要换算,假设LCD的高度是V。
在LCD坐标系中坐标是(x, y),那么它在笛卡尔坐标系中的坐标值为(x, V-y)。
反过来也是一样的,在笛卡尔坐标系中坐标是(x, y),那么它在LCD坐标系中坐标值为(x, V-y)。
1.6.2 每个字符的大小可能不同
在使用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结构体中,以后想计算一行文字的外框时要用到这些信息:
1.6.3 怎么在指定位置显示一行文字
要显示一行文字时,每一个字符都有自己外框: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’)。
1.6.4 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 );
针对上述流程,示例代码如下:
1.6.5 计算一行文字的外框
前面提到过,一行文字中:后一个字符的原点=前一个字符的原点+advance。
所以要计算一行文字的外框,需要按照排列顺序处理其中的每一个字符。
代码如下,注释写得很清楚了:
102 int compute_string_bbox(FT_Face face, wchar_t *wstr, FT_BBox *abbox) 103 { 104 int i; 105 int error; 106 FT_BBox bbox; 107 FT_BBox glyph_bbox; 108 FT_Vector pen; 109 FT_Glyph glyph; 110 FT_GlyphSlot slot = face->glyph; 111 112 /* 初始化 */ 113 bbox.xMin = bbox.yMin = 32000; 114 bbox.xMax = bbox.yMax = -32000; 115 116 /* 指定原点为(0, 0) */ 117 pen.x = 0; 118 pen.y = 0; 119 120 /* 计算每个字符的bounding box */ 121 /* 先translate, 再load char, 就可以得到它的外框了 */ 122 for (i = 0; i < wcslen(wstr); i++) 123 { 124 /* 转换:transformation */ 125 FT_Set_Transform(face, 0, &pen); 126 127 /* 加载位图: load glyph image into the slot (erase previous one) */ 128 error = FT_Load_Char(face, wstr[i], FT_LOAD_RENDER); 129 if (error) 130 { 131 printf("FT_Load_Char error\n"); 132 return -1; 133 } 134 135 /* 取出glyph */ 136 error = FT_Get_Glyph(face->glyph, &glyph); 137 if (error) 138 { 139 printf("FT_Get_Glyph error!\n"); 140 return -1; 141 } 142 143 /* 从glyph得到外框: bbox */ 144 FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &glyph_bbox); 145 146 /* 更新外框 */ 147 if ( glyph_bbox.xMin < bbox.xMin ) 148 bbox.xMin = glyph_bbox.xMin; 149 150 if ( glyph_bbox.yMin < bbox.yMin ) 151 bbox.yMin = glyph_bbox.yMin; 152 153 if ( glyph_bbox.xMax > bbox.xMax ) 154 bbox.xMax = glyph_bbox.xMax; 155 156 if ( glyph_bbox.yMax > bbox.yMax ) 157 bbox.yMax = glyph_bbox.yMax; 158 159 /* 计算下一个字符的原点: increment pen position */ 160 pen.x += slot->advance.x; 161 pen.y += slot->advance.y; 162 } 163 164 /* return string bbox */ 165 *abbox = bbox; 166 }
1.6.6 调整原点并绘制
代码如下,也不复杂:
169 int display_string(FT_Face face, wchar_t *wstr, int lcd_x, int lcd_y) 170 { 171 int i; 172 int error; 173 FT_BBox bbox; 174 FT_Vector pen; 175 FT_Glyph glyph; 176 FT_GlyphSlot slot = face->glyph; 177 178 /* 把LCD坐标转换为笛卡尔坐标 */ 179 int x = lcd_x; 180 int y = var.yres - lcd_y; 181 182 /* 计算外框 */ 183 compute_string_bbox(face, wstr, &bbox); 184 185 /* 反推原点 */ 186 pen.x = (x - bbox.xMin) * 64; /* 单位: 1/64像素 */ 187 pen.y = (y - bbox.yMax) * 64; /* 单位: 1/64像素 */ 188 189 /* 处理每个字符 */ 190 for (i = 0; i < wcslen(wstr); i++) 191 { 192 /* 转换:transformation */ 193 FT_Set_Transform(face, 0, &pen); 194 195 /* 加载位图: load glyph image into the slot (erase previous one) */ 196 error = FT_Load_Char(face, wstr[i], FT_LOAD_RENDER); 197 if (error) 198 { 199 printf("FT_Load_Char error\n"); 200 return -1; 201 } 202 203 /* 在LCD上绘制: 使用LCD坐标 */ 204 draw_bitmap( &slot->bitmap, 205 slot->bitmap_left, 206 var.yres - slot->bitmap_top); 207 208 /* 计算下一个字符的原点: increment pen position */ 209 pen.x += slot->advance.x; 210 pen.y += slot->advance.y; 211 } 212 213 return 0; 214 }
1.6.7 上机实验
编译命令(如果你使用的交叉编译链前缀不是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”。