本章主要內容如下: 1)矢量字體原理 2)使用freetype庫實現矢量字體顯示 1. 矢量字體原理 將漢字的筆劃邊緣用直線段描述成封閉的曲線,並將線段各端點的坐標經壓縮存儲,如下圖所示: 由於每個漢字的筆劃不一樣,從而每個漢字數據長度也不同,所以只能採用索引的方法。因而每種矢量字型檔都是由兩部分組成 ...
本章主要內容如下:
- 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 <ft2build.h>
#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中
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<wchar.h>
通過wcslen()判斷wchar_t數組大小
修改example1.c
... #include<wchar.h> //添加此行 ... int main( int argc,char** argv ) { ... ... wchar_t *chinese_str=L"韋東山g"; //添加此行 ... ... for ( n = 0; n <wcslen(chinese_str); 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 <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> #include <sys/ioctl.h> #include <string.h> #include <linux/fb.h> #include <math.h> #include<wchar.h> #include <ft2build.h> #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點陣大小的字體)