点阵显示有局限性,如字形固定,大小固定,为解决这些问题,本文介绍应用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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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做的简单的电子书的效果给大家分享一下。