點陣顯示有侷限性,如字形固定,大小固定,爲解決這些問題,本文介紹應用freetype來顯示字體。FreeType庫是一個開源的、可移植的字體引擎,它功能強大,簡單說就是支持字體的花樣顯示。
pc上安裝freetype庫:
1、首先自行下載freetype壓縮包,我下載的是freetype-2.4.10.tar.bz2
2、解壓:tar xjf freetype-2.4.10.tar.bz2
3、進入freetype目錄:cd freetype
4、./configure
5、make
6、sudo make install
這就在pc上安裝好了freetype。
如果編寫好了一個測試freetype的應用程序example.c,則編譯的時候要指定頭文件目錄/usr/local/include/freetype2,鏈接freetype庫和數學庫。同時建議加上-finput-charset=xxx編譯選項以指定輸入文件字符集的編碼方式,和-fexec-charset=xxx選項指定輸出的可執行文件的字符集編碼方式,不指定字符編碼方式的話,可能會出錯。如:
gcc -finput-charset=GBK -fexec-charset=UTF-8 -o example example.c -I /usr/local/include/freetype2 -lfreetype -lm
之所以要指定頭文件目錄/usr/local/include/freetype2,是因爲默認加載頭文件的目錄和頭文件的實際存儲目錄有點出入。如對ftheader.h文件,其:
默認加載目錄:freetype/config/ftheader.h
實際存儲目錄: freetype2/freetype/config/ftheader.h
如果要在arm開發板上使用freetype顯示字體,則按如下步驟安裝:
1、./configure的時候要設置 host 選項:
./configure --host=arm-linux
2、make
3、make DESTDIR=$PWD/tmp install //指定安裝路徑./tmp
爲方便編譯arm開發板上跑的應用程序,可以把安裝路徑下產生的庫文件複製到交叉編譯工具鏈下:
即:sudo cp tmp/usr/local/lib/* /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib -d -rf
sudo cp tmp/usr/local/include/freetype2/* /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include -rf
到此,如果有一個開發板上的測試程序example.c,就可以這樣編譯了,不用指定頭文件目錄和鏈接庫目錄:
arm-linux-gcc -finput-charset=GBK -fexec-charset=GBK -o example example.c -lfreetype -lm
把安裝目錄下生產的所有*so*文件複製到開板的根目錄的lib目錄下。我的根目錄是網絡文件系統/work/nfs_root/fs_mini_mdev_new,直接拷貝即可:
cp tmp/usr/local/lib/*so* /work/nfs_root/fs_mini_mdev_new/lib -d
開發板的/lib/ 目錄下有了這些 *so* 文件,freetype的應用程序就可以在開發板上跑了。
下面就是要寫一個example來測試一下freetype了。把freetype使用起來還是很簡單的,調用幾個freetype提供的函數就可以搞事情了。當然要有一些freetype的基礎理論知識,這我就不怎麼詳細說了,讀一下其官方文檔即可。
顯示一個字符:
步驟:
0.初始化FT_Init_Freetype()
1.給定一個文字‘A’的ASCII碼0x41
2.提供一個字體文件
3.根據編碼值到字體文件加載對應的glyph ( glyph含有關鍵點,相對位置。實際上是根據字符的編碼類型和編碼值找到glyph,
不同的編碼類型的字符,要用不同的charmap來尋找其glyph,一個charmap支持一種編碼,即有多個charmap以支持多種編碼)
可理解爲字體文件含有charmap和字的對應glyph
4.設置字體大小
5.用某些函數把glyph裏的關鍵點縮放爲這個字體大小
6.轉換爲位圖點陣(最終還是點陣)
7.在LCD上顯示出來
下面是一個在開發板LCD上用freetype顯示acsii字符的例子:
example.c:
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/fb.h>
#include <fcntl.h>
#include <ft2build.h>
#include FT_FREETYPE_H
int fd_fb;
struct fb_var_screeninfo var; /* Current var */
struct fb_fix_screeninfo fix; /* Current fix */
int screen_size;
unsigned char *fbmem;
unsigned int line_width;
unsigned int pixel_width;
//在LCD上顯示
/* color : 0x00RRGGBB */
void lcd_put_pixel(int x, int y, unsigned int color)
{
unsigned char *pen_8 = fbmem+y*line_width+x*pixel_width;
//找到對應的像素在內存中的位置,後面向其(*pen_8)寫顏色值。
//通過pen_8來操作fbmem,實現LCD顯示
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;
switch (var.bits_per_pixel)//不同的像素位數,顏色值格式不同。根據位數設置顏色值格式,並寫值。
{//像素的各位就代表着顏色分量值。
case 8:
{
*pen_8 = color;//8位像素的話,顏色值直接賦給這個內存中即可。
break;
}
case 16:
{
/* color : 0x00RRGGBB ,unsigned int 32位color的格式*/
/* 565 */
red = (color >> 16) & 0xff;//右移16位後剩下高16位,再&0xff,又剩下低8位。即取出32位color的16-23位
green = (color >> 8) & 0xff;//取出32color的8-15位
blue = (color >> 0) & 0xff;//取出32color的0-7位
color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);//取出對應的組成565
*pen_16 = color;
break;
}
case 32:
{
*pen_32 = color;//32位像素也直接寫即可。
break;
}
default:
{
printf("can't surport %dbpp\n", var.bits_per_pixel);
break;
}
}
}
//freetype聲明的一個函數指針draw_bitmap,需要我們自己實現這個函數,根據bitmap把字形描繪出來
//draw_bitmap()函數:把字體在屏上顯示,具體屏具體實現:
void
draw_bitmap( FT_Bitmap* bitmap, //draw_bimap()是把一個字符(一個字符對應的方框所有像素所對應的顏色值,
//顏色值是8位,16位等,即unsigned char,char等)寫到LCD的framebuffer。
FT_Int x,
FT_Int y)
{
FT_Int i, j, p, q;
FT_Int x_max = x + bitmap->width;
FT_Int y_max = y + bitmap->rows;
for ( i = x, p = 0; i < x_max; i++, p++ )
{
for ( j = y, q = 0; j < y_max; j++, q++ )
{
if ( i<0 || j<0 || i>=var.xres || j>=var.yres )
continue;
lcd_put_pixel(i, j, bitmap->buffer[q * bitmap->width + p]);
//顯示順序是一列一列的顯示,把i=x這一列,不同的y的這一列先顯示。
//即順序:(i,j,q*bitmap->width +p); (i,j+1, (q+1)*bitmap->width +p ) //(x,y,color)
//bitmap->buffer[]是顏色值,lcd_put_pixel()是操作lcd的應用層緩衝區framebuffer.
//來直接顯示顏色。
}
}
}
int lcd_init(void)
{
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;
}
if (ioctl(fd_fb, FBIOGET_FSCREENINFO, &fix))
{
printf("can't get fix\n");
return -1;
}
line_width = var.xres * var.bits_per_pixel / 8;
pixel_width = var.bits_per_pixel / 8;
screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
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;
}
}
int
main( int argc,char** argv )
{
FT_Library library;
FT_Face face;
FT_GlyphSlot slot;
FT_Matrix matrix; /* transformation matrix */
FT_Vector pen; /* untransformed origin */
FT_Error error;
char* filename;
char* text;
double angle;
int target_height;
int n, num_chars;
if ( argc != 3 )
{
fprintf ( stderr, "usage: %s font sample-text\n", argv[0] );
exit( 1 );
}
lcd_init();
/* 清屏: 全部設爲黑色 */
memset(fbmem, 0, screen_size);
filename = argv[1]; /* first argument */
text = argv[2]; /* second argument */
num_chars = strlen( text );
angle = ( 0.0 / 360 ) * 3.14159 * 2; /* use 25 degrees */
//旋轉的角度
//這個相當於像素數
target_height = var.yres;
error = FT_Init_FreeType( &library ); /* initialize library */
error = FT_New_Face( library, argv[1], 0, &face ); /* create face object */
#if 0
/* use 50pt at 100dpi */
error = FT_Set_Char_Size( face, 50 * 64, 0, 100, 0 ); /* set character size */
/* pixels = 50 /72 * 100 = 69 */
#else
FT_Set_Pixel_Sizes(face, 24, 0);
#endif
slot = face->glyph; //先使slot指向face->glyph,下面也是通過glyph來設置slot
/* set up matrix */
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 );
/* the pen position in 26.6 cartesian space coordinates; */
/* start at (0,40) relative to the upper left corner */
pen.x = 0 * 64;
pen.y = ( target_height - 40 ) * 64; //(0,40是LCD座標,轉爲笛卡爾),target_height是以dip爲單位的分辨率。
//這個pen.x = 0 * 64是char_width值,是FT_Set_Char_Size()函數參數中的50*64一樣,
//0,50都是LCD座標,總共80*80個座標(分辨率)。
//char_width與座標值的關係:char_width=座標值*64,char_width/64=n point,座標值就代表第 n
//個point,第n個分辨率單元。1分辨率就是1point
for ( n = 0; n < num_chars; n++ )
{
/* set transformation */
FT_Set_Transform( face, &matrix, &pen );//這裏使用到了pen
/* load glyph image into the slot (erase previous one) */
error = FT_Load_Char( face, text[n], FT_LOAD_RENDER );//就會設置face->glyph,轉換的位圖又保存在哪裏?
if ( error ) //位圖點陣應該是保存在face->glyph.bitmap中。
continue; /* ignore errors */
/* now, draw to our target surface (convert position) */
draw_bitmap( &slot->bitmap,
slot->bitmap_left,
target_height - slot->bitmap_top );
/* increment pen position */
pen.x += slot->advance.x;
pen.y += slot->advance.y;
}
FT_Done_Face ( face );
FT_Done_FreeType( library );
return 0;
}
編譯:arm-linux-gcc -finput-charset=GBK -fexec-charset=GBK -o example example.c -lfreetype -lm
測試:
1、把可執行文件放在網絡文件系統:cp example /work/nfs_root/fs_mini_mdev_new/
2、運行程序時需要提供一個字體文件,我的字體文件是simsun.ttc
3、開發板上運行可執行程序:./example ./simsun.ttc https://blog.csdn.net/qq_22863733顯示漢字:
顯示效果:
顯示漢字:
main()中添加的代碼:
wchar_t *chinese_str = L"個人博客";//要顯示的漢字
//wcslen()類似與char*類型作參數的strlen()函數,用來獲取wchar_t*變量的長度(字符個數爲單位,不是字節數(不包含終結符)
for ( n = 0; n < wcslen(chinese_str); n++ )
{
FT_Set_Transform( face, &matrix, &pen );//這裏使用到了pen
error = FT_Load_Char( face, chinese_str[n], FT_LOAD_RENDER );//chinese_srt[n]實際上是漢字的編碼,本例是是GBK碼。
if (error)
{
printf("FT_Load_Char error\n");
return -1;
}
draw_bitmap( &slot->bitmap,
slot->bitmap_left,
var.yres - slot->bitmap_top);
pen.x += slot->advance.x;
pen.y += slot->advance.y;
}
pen.x = 0;
pen.y = pen.y + 24*64; //在新的一行顯示ascii字符,pen.y 、pen.x 爲笛卡爾座標
編譯、測試,效果:
freetype顯示字體的重點是獲得字符的編碼,然後FT_Load_Char( face, chinese_str[n], FT_LOAD_RENDER );有了加載了glyph的face後,就可以吧字形draw_bitmap出來了。利用freetype實現電子書就是這樣的原理,電子書文件保存了字符的編碼值,編碼可能是ANSI,utf-8等,可以把編碼先轉換爲UIcode碼再FT_Load_Char,或在FT_Load_Char時指定字符編碼方式。這需要一些字符編碼方面的知識,有機會再說吧,下面是我用freetype做的簡單的電子書的效果給大家分享一下。