本章主要内容如下:
- 1)矢量字体原理
- 2)使用freetype库实现矢量字体显示
1. 矢量字体原理
将汉字的笔划边缘用直线段描述成封闭的曲线,并将线段各端点的坐标经压缩存储,如下图所示:
由于每个汉字的笔划不一样,从而每个汉字数据长度也不同,所以只能采用索引的方法。因而每种矢量字库都是由两部分组成,一部分是汉字的索引信息,一部分是汉字的字形(glyph)数据.
当显示文字时,便提取出各端点,并通过贝塞尔曲线来连接各个坐标,最后填充封闭空间.
接下来便使用freetype库制作矢量字体
2. freetype-2.4.10库
freetype库是一个开源的字体引擎,支持多种字符集编码(utf-8等).
freetype库下载: https://sourceforge.net/projects/freetype/files/freetype2/2.4.10/
freetyoe英文参考文档下载:https://sourceforge.net/projects/freetype/files/freetype-docs/2.4.10/
FreeType 中文使用参考:
http://wenku.baidu.com/view/2d24be10cc7931b765ce155b.html
https://wenku.baidu.com/view/e7149f6748d7c1c708a14574.html
2.1如何来使用freetype
1)包含头文件:
#include#include FT_FREETYPE_H
2) 初始化库:
使用FT_Init_FreeType()函数初始化一个FT_Library类型的变量,例如:
FT_LIBRARY library; //库的句柄 error = FT_Init_FreeType( &library ); if ( error ) { //初始化失败 } ... ...
3)加载face对象:
通过FT_NEW_Face()打开一个字体文件,然后提取该文件的一个FT_Face类型的face变量,
例如:
FT_LIBRARY library; //库的句柄 FT_Face face; /* face对象的句柄 */ error = FT_Init_FreeType ( &library ); if ( error ) {... ...} ... ... error = FT_New_Face( library, "/usr/share/fonts/truetype/arial.ttf", //字形文件 0, &face );
4)设置字体大小(参考freetype-2.4.10/docs/reference/ft2-base_interface.html):
方法1:
FT_Set_Char_Size( FT_Face face, FT_F26Dot6 char_width, //字符宽度,单位为1/64点 FT_F26Dot6 char_height, //字符高度,单位为1/64点 FT_UInt horz_resolution, //水平分辨率 FT_UInt vert_resolution ); //垂直分辨率
字符宽度和高度以1/64点为单位表示。点是物理上的距离,一个点代表1/72英寸(2.54cm)
分辨率以dpi(dots per inch)为单位表示,表示一个英寸有多少个像素
例如:
error = FT_Set_Char_Size( face, 50 * 64, 0,100, 0 ); //0表示与另一个尺寸值相等。
得出:
字符物理大小为: 50*64* (1/64) * (1/72)英寸
字符的像素为: 50*64* (1/64) * (1/72)*100
方法2:
FT_Set_Pixel_Sizes( FT_Face face, FT_UInt pixel_width, //像素宽度 FT_UInt pixel_height ); //像素高低
例如:
error = FT_Set_Pixel_Sizes( face, 0,16); //把字符像素设置为16*16像素, 0表示与另一个尺寸值相等。
5)设置字体位置,以及旋转度数(不设置的话表示原点位于0,0):
error = FT_Set_Transform( face, /* 目标face对象 */ &matrix, /* 指向2x2矩阵的指针,写0表示不旋转,使用正矩形 */ &delta ); /*字体坐标位置(用的笛卡尔坐标),以1/64像素为单位表示,写0表示原点是(0,0) */
由于我们LCD的坐标原点是位于左上方
笛卡尔坐标:表示坐标原点位于左下方(与LCD的y轴相反)
所以转换之前填写坐标时,需要转换一下y轴值(总高度-y)
转换成功后还需要转换回来(总高度-y)
比如,旋转25,并在(300,200)处显示:
FT_Vector pen; /* */ FT_Matrix matrix; /* transformation matrix */ angle = ( 25.0 / 360 ) * 3.14159 * 2; /* use 25 degrees */ /*将该文字坐标转为笛卡尔坐标*/ pen.x = 300 * 64; pen.y = ( target_height - 200 ) * 64; // target_height: LCD总高度 //设置 矩形参数 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 );
6)加载字形图像
a.获取编码的索引
通过FT_Get_Char_Inde()函数将字符编码转换为一个字形(glyph)索引 (Freetype默认是utf-16编码类型)
例如:
glyph_index = FT_Get_Char_Index( face, charcode );
若glyph_index为NULL,表示没找到字形(glyph)索引
如果使用其它字符编码,则通过FT_Select_Charmap()来获取,例如获取big5编码:
error = FT_Select_Charmap( face, /* 目标face对象 */ FT_ENCODING_BIG5 ); /* big5编码 */ //FT_ENCODING_BIG5枚举定义在FT_FREETYPE_H中
//FT_ENCODING_GB2312 :GB2312编码
//该函数头文件位于:FT_FREETYPE_H (freetype/freetype.h).
b.通过索引,从face中加载字形
获得字形索引后,接下来便根据字形索引,来将字形图像存储到字形槽(glyph slot)中.
字形槽:每次只能存储一个字形图像,每个face对象都有一个字形槽,位于face->glyph
通过FT_Load_Glyph()来加载一个字形图像到字形槽:
error = FT_Load_Glyph( face, /* face对象的句柄 */ glyph_index, /* 字形索引 */ load_flags ); /* 装载标志,一般填FT_LOAD_DEFAULT*/
并更新face->glyph下的其它成员,比如:
FT_Int bitmap_left; //该字形图像的最左边的X值 FT_Int bitmap_top; //该字形图像的最上边的Y值
c.转为位图
通过FT_Render_Glyph()函数,将字形槽的字形图像转为位图,并存到 face->glyph->bitmap->buffer[]里
error = FT_Render_Glyph( face->glyph, /* 字形槽 */ render_mode ); /* 渲染模式 */
render_mode标志可以设为以下几种:
FT_RENDER_MODE_NORMAL:表示生成位图每个像素是RGB888的
FT_RENDER_MODE_MONO :表示生成位图每个像素是1位的(黑白图)
并更新face->glyph->bitmap下的其它成员,比如:
int rows; //该位图总高度,有多少行 int width; //该位图总宽度,有多少列像素点 int pitch: //指一行的数据跨度(字节数),比如对于24位(3字节)的24*30汉字,则pitch=24*3 char pixel_mode //像素模式,1 指单色的,8 表示反走样灰度值 unsigned char* buffer //glyph 的点阵位图内存绶冲区
d.也可以直接使用FT_Load_Char()来代替FT_Get_Char_Index()、FT_Get_Load_Glyph()和FT_Render_Glyph().
例如:
error = FT_Load_Char( face, charcode, FT_LOAD_RENDER );
其中FT_LOAD_RENDER:表示直接将图像转为位图,所以不需要使用FT_Render_Glyph()函数
该函数默认生成的位图是默认生成的FT_RENDER_MODE_NORMAL类型,RGB888的
若想生成FT_RENDER_MODE_MONO(黑白图)类型,操作如下:
error = FT_Load_Char( face, charcode, FT_LOAD_RENDER | FT_LOAD_MONOCHROME );
生成出来的位图像素,每8个像素点便表示 face->glyph->bitmap->buffer[]里的一个字节.
2.2参考example1.c例程
example1.c位于freetype-doc-2.4.10.tar.bz2\freetype-2.4.10\docs\tutorial下
3.在PC虚拟机里编译例程:example1.c
3.1安装freetype到/usr/local/里(拿给PC用)
tar -xjf freetype-2.4.10.tar.bz2 mv freetype-2.4.10 freetype-2.4.10_pc cd freetype-2.4.10_pc/ ./configure //配置 make //编译 sudo make install //直接将库安装到根目录/usr/local/里,所以需要加sudo
由于example1.c的打印范围是640*480,而我们secureCRT没有那么大,所以修改example1.c.
将:
#define WIDTH 640 #define HEIGHT 480
改为:
#define WIDTH 80 #define HEIGHT 80
然后将119行处的文字显示坐标:
pen.x = 300 * 64; pen.y = ( target_height - 200 ) * 64;
改为:
pen.x = 0 * 64; //在坐标(0,40)处显示 pen.y = ( target_height - 40 ) * 64;
3.2 编译运行
gcc -o example1 example1.c
编译出错:
通过ls,发现又有这个文件:
所以通过-I,直接指定头文件目录:
gcc -o example1 example1.c -I /usr/local/include/freetype2/
编译再次出错:
发现这些出错的都是函数,其中FT开头的是freetype库的函数,cos等都是数学库的函数,
freetype库的文件名是 libfreetype.so
数学库的文件名是libm.so
所以编译时,加上-l,指定库文件:
gcc -o example1 example1.c -I /usr/local/include/freetype2/ -lfreetype -lm
3.3 运行example1
将C:\Windows\Fonts下的simsun.ttc(宋体)字体文件拷到虚拟机里,输入./example1 simsun.ttc agf,发现是斜的:
这是因为example1.c里通过FT_Set_Transform()设置了字体旋转
3.4 继续修改example1.c
关闭字体旋转,将
FT_Set_Transform( face, &matrix, &pen );
改为
FT_Set_Transform( face, 0, &pen );
修改字体大小,将
error = FT_Set_Char_Size( face, 50 * 64, 0, 100, 0 );
改为:
error = FT_Set_Pixel_Sizes( face, 24, 0 ); //24*24像素
编译运行:
3.5 显示汉字
如果用char存储汉字英文等,则还需要判断数据类型,而wchar_t刚好可以放一个unicode字符。
注意:wchar_t在windows占2byte,在linux占4bytes.
宽字符:wchar_t
头文件: #include
通过wcslen()判断wchar_t数组大小
修改example1.c
... #include//添加此行 ... int main( int argc,char** argv ) { ... ... wchar_t *chinese_str=L"韦东山g"; //添加此行 ... ... for ( n = 0; n //修改此行 { FT_Set_Transform( face, 0, &pen ); //字体转换 /* load glyph image into the slot (erase previous one) */ error = FT_Load_Char( face, chinese_str[n], FT_LOAD_RENDER ); //修改此行 ... ... } return 0; }
通过另存为文件,来看看文件本身是什么编码格式
如下图所示,看到是ANSI编码, 对于中文PC,ANSI编码对应的是GBK编码:
linux默认是utf-8编码,所以编译时,需要指定字符集:
gcc -o example1 example1.c -I /usr/local/include/freetype2/ -lfreetype -lm -finput-charset=GBK -fexec-charset=utf-8 // -finput-charset:告诉编译器,文件里的字符是GBK格式 //-fexec-charset:告诉编译器,需要先将里面的内容转换为utf-8格式后,再来编译
运行代码:
添加坐标打印信息:
发现,我们打印坐标是在(40,0),为什么文字坐标还会超过原点?,参考以下图所示:
advance: 位于face->glyph-> advance,用来存放每个文字之间的间隔信息,每当加载一个新的图像时,系统便会更新该数据.
3.6 获取位图文字的信息
当我们每次将新的字形图像(face->glyph)转为位图后,而存放的前一个字形图像就会被删除.
当有时候,有可能需要提取字形图像的坐标,该怎么做?
1)首先添加头文件:
#include FT_GLYPH_H
2)通过FT_Get_Glyph()将一个字形图像(face->glyph)存到FT_Glyph类型的变量里,例如:
FT_Glyph glyph; /* a handle to the glyph image */ ... error = FT_Load_Glyph( face, glyph_index, FT_LOAD_NORMAL ); //通过字符编码,获取字形图像存到face->glyph里,并转为位图存到face->glyph->bitmap->buffer[]里 if ( error ) { ... } error = FT_Get_Glyph( face->glyph, &glyph ); //将字形图像(face->glyph)存到glyph里 if ( error ) { ... }
3) 通过FT_Glyph_Get_CBox()获取文字的xMin, xMax, yMin, yMax坐标信息
参考: /freetype-2.4.10/docs/reference/ft2-index.html
FT_Glyph_Get_CBox( FT_Glyph glyph, //该值通过FT_Get_Glyph()来获取 FT_UInt bbox_mode, //模式,填入FT_GLYPH_BBOX_TRUNCATE即可 FT_BBox *acbox ); //用来存放获取到的xMin, xMax, yMin, yMax信息
其中FT_GLYPH_BBOX_TRUNCATE表示:获取的坐标信息是像素坐标,而不是点坐标
修改example1.c,使它能打印每个汉字的坐标信息:
#include FT_GLYPH_H //添加此行 ... ... int main( int argc, char** argv ) { FT_Glyph glyph; FT_BBox acbox; ... ... for ( n = 0; n < wcslen(chinese_str); n++ ) { ... ... error = FT_Load_Char( face,chinese_str[n], FT_LOAD_RENDER ); if ( error ) continue; /* ignore errors */ error = FT_Get_Glyph( face->glyph, &glyph ); //添加此行 FT_Glyph_Get_CBox( glyph,FT_GLYPH_BBOX_TRUNCATE,&acbox ); //添加此行 printf("0x%x:xMin=%ld,xMax=%ld,yMin=%ld,yMax=%ld\n",chinese_str[n],acbox.xMin,acbox.xMax,acbox.yMin,acbox.yMax); //添加此行
... ...
编译运行:
表示韦字(97e6)的笛卡尔坐标 : X坐标在0~23,y坐标在37~60,是个24*24字体.
由于笛卡尔坐标的原点坐标位于左下方.
所以对应韦字(97e6)的LCD坐标: X坐标在0~23 ,y坐标为20~43
4.在LCD上显示矢量文字
安装freetype到交叉编译目录里(供arm-linux-gcc编译)
4.1首先查看,需要安装到哪个lib和include目录
1)通过$PATH找到arm-linu-gcc交叉编译位于:
/work/tools/arm-linux-gcc-4.3.2/usr/local/arm/4.3.2/bin
然后进入.../arm/4.3.2/目录,通过find查找stdio.h文件,找到:
所以编译出来的头文件应该放入:
/work/tools/arm-linux-gcc-4.3.2/usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include
2)通过find查找lib,找到:
由于ARM9属于ARMv4T架构,所以编译出来的库文件应该放入:
/work/tools/arm-linux-gcc-4.3.2/usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib
4.2安装
参考:freetype-2.4.10/docs/INSTALL.CROSS
tar -xjf freetype-2.4.10.tar.bz2 mv freetype-2.4.10 freetype-2.4.10_arm cd freetype-2.4.10_arm mkdir tmp //创建安装的临时目录,后面会拷贝到交叉编译目录里 ./configure --host=arm-linux --prefix=$PWD/tmp //配置交叉编译,安装前缀 make make install cd tmp/ cp ./include/* /work/tools/arm-linux-gcc-4.3.2/usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include/ -rfd
//将include下的头文件拷贝到交叉编译里去 cp lib/* /work/tools/arm-linux-gcc-4.3.2/usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib/ -rfd
//将lib下的库文件拷贝到交叉编译里去 cp lib/ * /work/nfs_root/3.4_fs_mini_mdev/lib/ -rfd
//将lib下的库文件拷贝到nfs文件系统去
为什么不拷贝头文件? 因为编译好了freetype程序后,头文件会被gcc展开存到可执行文件里,所以运行时,只会用到库文件.
4.3写代码(参考上章代码和example1.c)
#include#include #include #include #include #include #include #include <string.h> #include #include #include #include #include FT_FREETYPE_H #include FT_GLYPH_H unsigned char *fbmem; unsigned char *hzkmem; struct fb_var_screeninfo fb_var; struct fb_fix_screeninfo fb_fix; unsigned int screensize; #define FONTDATAMAX 4096 static const unsigned char fontdata_8x16[FONTDATAMAX] = { //ASCII码点阵太长,省略... };
/*rgb565*/ void pixel_show(int x,int y, unsigned int color) { unsigned int red,green,blue; switch(fb_var.bits_per_pixel) //rgb 像素 { case 32: { unsigned int *addr=(unsigned int *)fbmem+(fb_var.xres*y+x); *addr=color; break; } case 24: { unsigned int *addr=(unsigned int *)fbmem+(fb_var.xres*y+x); *addr=color; break; } case 16: //将RGB888 转为RGB565 { unsigned short *addr=(unsigned short *)fbmem+(fb_var.xres*y+x); red = (color >> 16) & 0xff; green = (color >> 8) & 0xff; blue = (color >> 0) & 0xff; color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3); *addr = color; break; } case 8: { unsigned char *addr=(unsigned char *)fbmem+(fb_var.xres*y+x); *addr = (unsigned char)color; break; } default: { printf("can't surport %dbpp \n",fb_var.bits_per_pixel); break; } } } /*显示ascii码*/ void lcd_put_char(int x,int y, unsigned char s) { unsigned char *index=(unsigned char *)&fontdata_8x16[s*16]; unsigned char i,j; for(i=0;i<16;i++) //8*16 for(j=0;j<8;j++) { //从高位到低 if(index[i]&(1<<(7-j))) //亮 pixel_show(x+j,y+i, 0xffffff); //白色 else //灭 pixel_show(x+j,y+i, 0x0); //黑色 } } /*显示GBK码*/ void lcd_put_chinese(int x,int y, unsigned char *s) { unsigned char i,j,k; //将编码转为区码 unsigned int index=(s[0]-0xA1)*94+(s[1]-0xA1); //转为点阵码(每个汉字32字节) unsigned char *dots=hzkmem+index*32; for(i=0;i<16;i++) //16*16 for(k=0;k<2;k++) for(j=0;j<8;j++) { if((dots[i*2+k]>>(7-j))&0X01) //亮 pixel_show(x+8*k+j,y+i, 0xffffff); //白色 else //灭 pixel_show(x+8*k+j,y+i, 0x0); //黑色 } } void lcd_put(int x,int y, unsigned char *s) { while(*s) { if(*s<0xA1) //ASCII码8*16 { printf("ASCII %x \r\n",*s ); lcd_put_char(x,y,*s); s+=1; x+=8; } else //GB2313 16*16 { printf("GBK %x %x\r\n",*s, *(s+1)); lcd_put_chinese(x,y,s); s+=2; x+=16; } } } void draw_bitmap( FT_Bitmap* bitmap, FT_Int x, FT_Int y) { FT_Int i, j, p, q; FT_Int x_max = x + bitmap->width; //x:当前X位置, bitmap->width:该字宽度 FT_Int y_max = y + bitmap->rows; for ( i = x, p = 0; i < x_max; i++, p++ ) //i:lcd的x轴 { for ( j = y, q = 0; j < y_max; j++, q++ ) //j:lcd的y轴 { if ( i < 0 || j < 0 || i >= fb_var.xres || j >= fb_var.yres ) continue; pixel_show( i, j, bitmap->buffer[q * bitmap->width + p]); } } } void lcd_vector_show(char *argv,wchar_t *str) { FT_Library library; FT_Face face; FT_GlyphSlot slot; FT_Vector pen; /* untransformed origin */ unsigned char error; unsigned char n,font_size;
error = FT_Init_FreeType( &library ); /* initialize library */ if(error) { printf("FT_Init_FreeType ERROR\n"); return ; } error = FT_New_Face( library, argv, 0, &face ); /* create face object */ if(error) { printf("FT_New_Face ERROR\n"); return ; } slot = face->glyph; /*显示坐标(从LCD中间显示) *x=fb_var.xres /2 *y=fb_var.yres-fb_var.yres/2-16 (减16,是因为笛卡尔坐标以左下方开始计算坐标值的) */ pen.x = fb_var.xres /2* 64; pen.y = ( fb_var.yres/2-16) * 64; for ( n = 0; n < wcslen(str); n++ ) { font_size=(n%6)*4+20; // 20*20 24*24 28*28 32*32 36*36 40*40 error = FT_Set_Pixel_Sizes( face, 0,font_size); /* set character size */ FT_Set_Transform( face, 0, &pen ); error = FT_Load_Char( face,str[n], FT_LOAD_RENDER ); if ( error ) { printf("FT_Load_Char ERROR\n"); continue; } draw_bitmap( &slot->bitmap, slot->bitmap_left, fb_var.yres- slot->bitmap_top ); pen.x += slot->advance.x; pen.y += slot->advance.y; } FT_Done_Face( face ); FT_Done_FreeType( library ); } int main(int argc,char **argv) { int fd_fb,fd_hzk; struct stat hzk_start; //HZK16文件信息 unsigned char s[]="abc 中国chinese"; wchar_t *chinese_str=L"韦东山g h "; if ( argc != 2 ) { printf ("usage: %s font_file \n", argv[0] ); return 0; } fd_hzk=open("HZK16",O_RDONLY); if(fd_hzk<0) { printf("can't open HZK16 \n"); return 0; } if(fstat(fd_hzk,&hzk_start)<0) //获取HZK16文件信息 { printf("can't get fstart \n"); return 0; } hzkmem =(unsigned char *)mmap(NULL,hzk_start.st_size, PROT_READ,MAP_SHARED, fd_hzk, 0); //映射HZK16文件 if(!hzkmem) { printf("can't map HZK16 \n"); return 0; } fd_fb=open("/dev/fb0", O_RDWR); if(fd_fb<0) { printf("can't open /dev/fb0 \n"); return 0; } if(ioctl(fd_fb,FBIOGET_VSCREENINFO,&fb_var)<0) { printf("can't get var \n"); return 0; } if(ioctl(fd_fb,FBIOGET_FSCREENINFO,&fb_fix)<0) { printf("can't get fix \n"); return 0; } screensize=fb_var.xres*fb_var.yres*(fb_var.bits_per_pixel/8); //显存大小 fbmem =(unsigned char *)mmap(NULL,screensize, PROT_READ|PROT_WRITE,MAP_SHARED, fd_fb, 0); //映射fb0 if(!fbmem) { printf("can't map /dev/fb0 \n"); return 0; } memset(fbmem, 0, screensize); //清屏黑色 /*显示数据*/ lcd_put(0,fb_var.yres/2,s); /*显示矢量文字*/ lcd_vector_show(argv[1], chinese_str); munmap(hzkmem,hzk_start.st_size); munmap(fbmem,screensize); return 0; }
4.4编译程序
编译报错: 56:38: error: freetype/config/ftheader.h: No such file or directory
通过find找到ftheader.h的位置是位于:../include/freetype2/freetype/config/ftheader.h
输入:
cd ./arm-none-linux-gnueabi/libc/usr/include/freetype2 mv freetype/ ../freetype //将freetype2下的freetype移到include目录下
编译:
arm-linux-gcc -o show_font show_font.c -lfreetype -lm -finput-charset=GBK -fexec-charset=GBK
运行:
(发现,显示16*16字体时,会乱码, 新宋字体simsun不支持16点阵大小的字体)
下章学习:4.数码相框-freetype多行显示,居中显示