文章目录
- 字符的编码方式
- 编码与字体
- ASCII
- ANSI
- UNICODE
- UNICODE 编码实现
- ASCII 字符的点阵显示
- ASCII字符文件
- 主要代码
- 中文字符的点阵显示
- 指定编码格式
- 汉字的点阵数据(汉字区位码)
- 交叉编译 freetype 矢量库
- 程序运行的一些基础知识
- 怎么确定“系统目录”?
- 交叉编译程序的万能命令
- 交叉编译freetype的依赖库
- 使用 freetype 显示一个文字
- wchar_t 宽字符
- Freetype库简介
- 在LCD上令矢量字体旋转某个角度
- 使用 freetype 显示一行文字
关闭开发板GUI:
systemctl stop myir
恢复开发板GUI:systemctl start myir
字符的编码方式
编码与字体
TXT 文件中保存的是字符的核心:它的编码值
字符对应什么样的形状态:字符文件决定
有以下三种编码方式:ASCII、ANSI(GB2312)、UNICODE、
ASCII
一个字节的 7 位就可以表示 128 个数值,在 ASCII 码中最高位永远是 0
ANSI
ASNI 是 ASCII 的扩展,向下包含 ASCII。对于 ASCII 字符仍以一个字节来表示,对于非 ASCII 字符则使用 2 字节来表示。并没有固定的 ASNI 编码,它跟“本地化”(locale)密切相关。比如在中国大陆地区,ANSI 的默认编码是 GB2312;在港澳台地区默认编码是 BIG5。所以对于 ANSI 编码的 TXT 文件,如果你打开它发现乱码,那么还得再次细分它的具体编码。
在简体中文系统下,ANSI 编码代表 GB2312 编码
UNICODE
对于地球上任意一个字符,都给它一个唯一的编码值。
UNICODE 仍然向下兼容 ASCII,但是对于其他字符会有对应的数值
UNICODE 中的数值范围是 0x0000 至 0x10FFFF,有 1,114,111 即 100 多万个数值,可以表示 100 多万个字符
UNICODE 编码实现
考虑到使用多少个字节去表示一个UNICODE,避免浪费,也避免无法识文断字
UCS-2 Little endian/UTF-16 LE:
- Little endian 表示小字节序,数值中权重低的字节放在前面
- 文件开头的“0xff 0xfe”表示“UTF-16 LE”。
UCS-2 Big endian/UTF-16 BE
- Big endian 表示大字节序,数值中权重低的字节放在后面
- 文件开头的“0xfe 0xff”表示“UTF-16 BE”。
UTF8
- 在上面 2 种方法中,每一个 UNICODE 使用 2 字节来表示,这有 3 个缺点:表示的字符数量有限、对于 ASCII 字符有空间浪费、如果文件中有某个字节丢失,这会使得后面所有字符都因为错位而无法显示。
- 使用 UTF8 可以解决上述所有问题。UTF8 是变长的编码方法,有 2 种 UTF8格式的文件:带有头部(带有BOM)、不带头部
- 对于其中的 ASCII 字符,在 UTF8 文件中直接用其 ASCII 码来表示
- 对于非 ASCII 字符,使用变长的编码:每一个字节的高位都自带长度信息
如图所示,将unicode编码值0x4e2d进行编码,得到UTF-8值
ASCII 字符的点阵显示
前面介绍了编码方式,采用UNICODE编码得到编码值,在字符集里面找到对应的点阵,遍历点阵就可以得到字符了
ASCII字符文件
要在 LCD 中显示一个 ASCII 字符,即英文字母这些字符,首先是要找到字符对应的点阵。在 Linux 内核源码中有这个文件:lib\fonts\font_8x16.c(8列16行),里面以数组形式保存各个字符的点阵,例如,字符A的表示:
上图左侧有 16 行数值,每行 1 个字节,一共16字节。每一个节对应右侧一行中 8 个像素:像素从右边数起,bit0 对应第 0 个像素,bit1 对应第 1 个像素,……,bit7对应第 7 个像素。某位的值为 1 时,表示对应的像素要被点亮;值为 0 时表示对应的像素要熄灭。
所以要显示某个字符时,根据它的 ASCII 码在 fontdata_8x16 数组中找到它的点阵,然后取出这 16 个字节去描画 16行像素
主要代码
省略 lib\fonts\font_8x16.c 里面关于点阵的代码
int fd_fb;
struct fb_var_screeninfo var; /* Current var */
int screen_size;
unsigned char *fbmem;
unsigned int line_width;
unsigned int pixel_width;
//功能描述: 在LCD指定位置上输出指定颜色(描点)
void lcd_put_pixel(int x, int y, unsigned int color)
{
unsigned char *pen_8 = fbmem+y*line_width+x*pixel_width;
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;
break;
}
case 16:
{
/* 565 */
red = (color >> 16) & 0xff;
green = (color >> 8) & 0xff;
blue = (color >> 0) & 0xff;
color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
*pen_16 = color;
break;
}
case 32:
{
*pen_32 = color;
break;
}
default:
{
printf("can't surport %dbpp\n", var.bits_per_pixel);
break;
}
}
}
//功能描述: 在LCD指定位置上显示一个8*16的字符
void lcd_put_ascii(int x, int y, unsigned char c)
{
unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16];//获取传入参数的点阵数组
int i, b;
unsigned char byte;
for (i = 0; i < 16; i++)//循环16行
{
byte = dots[i];
for (b = 7; b >= 0; b--)//循环8列
{
if (byte & (1<<b))
{
/* show */
/* 首先要启动Frambuffer */
lcd_put_pixel(x+7-b, y+i, 0xffffff); /* 白 */
}
else
{
/* hide */
lcd_put_pixel(x+7-b, y+i, 0); /* 黑 */
}
}
}
}
int main(int argc, char **argv)
{
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))//获取 Framebuffer 参数
{
printf("can't get var\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;
}
/* 清屏: 全部设为黑色 */
memset(fbmem, 0, screen_size);
lcd_put_ascii(var.xres/2, var.yres/2, 'A'); /*在屏幕中间显示8*16的字母A*/
munmap(fbmem , screen_size);
close(fd_fb);
return 0;
}
中文字符的点阵显示
指定编码格式
无论在编写c程序时的编码格式是什么,在编译的时候都可以指定编码格式
我们编写 C 程序时,可以使用 ANSI 编码,或是 UTF-8 编码;在编译程序时,可以使用以下的选项告诉编译器:
在简体中文系统下,ANSI 编码代表 GB2312 编码
-finput-charset=GB2312
-finput-charset=UTF-8
GCC 就会默认 C 程序的编码方式为UTF-8,即使你是以 ANSI 格式保存,也会被当作 UTF-8 来对待
对于编译出来的可执行程序,可以指定它里面的字符是以什么方式编码,可以使用以下的选项编译器:
-fexec-charset=GB2312
-fexec-charset=UTF-8
如果不指定“-fexec-charset”,GCC 就会默认编译出的可执行程序中字符的编码方式为 UTF-8。
如果“-finput-charset”与“-fexec-charset”不一样,编译器会进行格式转换。
汉字的点阵数据(汉字区位码)
使用 HZK16 这个文件,它是常用汉字的 16*16 点阵字库。
汉字使用GB2312索引,所以编码时必须转化为GB2312格式
HZK16 里每个汉字使用 32 字节来描述
使用区码与位码找到该汉字所在点阵的位置
-
HZK16 中是以 GB2312 编码值来查找点阵的,以“中”字为例,它的编码值是“0xd6 0xd0”,其中的 0xd6 表示“区码”,表示在哪一个区:第“0xd6- 0xa1”区;其中的 0xd0 表示“位码”,表示它是这个区里的哪一个字符:第“0xd0 - 0xa1”个。每一个区有 94 个汉字。区位码从 0xa1 而不是从 0 开始,
是为了兼容 ASCII 码,避免冲突,从0xa1开始
。所以,我们要显示的“中”字,它的 GB2312 编码是 d6d0,它是 HZK16 里第“(0xd6-0xa1)*94+(0xd0-0xa1)”个字符
。 -
每个区中有 94 个汉字,每个汉字在字库中占据 32 字节。所以,((0xd6-0xa1)*94+(0xd0-0xa1))*32得到该汉字在字符集里面的偏移地址值
主要代码如下:省略fontdata_8x16。
int fd_fb;
struct fb_var_screeninfo var; /* Current var */
int screen_size;
unsigned char *fbmem;
unsigned int line_width;
unsigned int pixel_width;
int fd_hzk16;
struct stat hzk_stat;
unsigned char *hzkmem;
/**********************************************************************
* 函数名称: lcd_put_pixel
* 功能描述: 在LCD指定位置上输出指定颜色(描点)
* 输入参数: x坐标,y坐标,颜色
* 输出参数: 无
* 返 回 值: 会
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2020/05/12 V1.0 zh(angenao) 创建
***********************************************************************/
void lcd_put_pixel(int x, int y, unsigned int color)
{
unsigned char *pen_8 = fbmem+y*line_width+x*pixel_width;
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;
break;
}
case 16:
{
/* 565 */
red = (color >> 16) & 0xff;
green = (color >> 8) & 0xff;
blue = (color >> 0) & 0xff;
color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
*pen_16 = color;
break;
}
case 32:
{
*pen_32 = color;
break;
}
default:
{
printf("can't surport %dbpp\n", var.bits_per_pixel);
break;
}
}
}
/**********************************************************************
* 函数名称: lcd_put_ascii
* 功能描述: 在LCD指定位置上显示一个8*16的字符
* 输入参数: x坐标,y坐标,ascii码
* 输出参数: 无
* 返 回 值: 无
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2020/05/12 V1.0 zh(angenao) 创建
***********************************************************************/
void lcd_put_ascii(int x, int y, unsigned char c)
{
unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16];
int i, b;
unsigned char byte;
for (i = 0; i < 16; i++)
{
byte = dots[i];
for (b = 7; b >= 0; b--)
{
if (byte & (1<<b))
{
/* show */
lcd_put_pixel(x+7-b, y+i, 0xffffff); /* 白 */
}
else
{
/* hide */
lcd_put_pixel(x+7-b, y+i, 0); /* 黑 */
}
}
}
}
/**********************************************************************
* 函数名称: lcd_put_chinese
* 功能描述: 在LCD指定位置上显示一个16*16的汉字
* 输入参数: x坐标,y坐标,ascii码
* 输出参数: 无
* 返 回 值: 无
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2020/05/12 V1.0 zh(angenao) 创建
***********************************************************************/
void lcd_put_chinese(int x, int y, unsigned char *str)
{
//str[0]中保存区码、
//str[1]中保存位码
unsigned int area = str[0] - 0xA1;
unsigned int where = str[1] - 0xA1;
unsigned char *dots = hzkmem + (area * 94 + where)*32;//偏移地址加上初始地址就是该汉字的起始地址
unsigned char byte;
int i, j, b;
for (i = 0; i < 16; i++)
for (j = 0; j < 2; j++)
{
byte = dots[i*2 + j];
for (b = 7; b >=0; b--)
{
if (byte & (1<<b))
{
/* show */
lcd_put_pixel(x+j*8+7-b, y+i, 0xffffff); /* 白 */
}
else
{
/* hide */
lcd_put_pixel(x+j*8+7-b, y+i, 0); /* 黑 */
}
}
}
}
int main(int argc, char **argv)
{
unsigned char str[] = "中";
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;
}
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;
}
//打开汉字库文件
fd_hzk16 = open("HZK16", O_RDONLY);
if (fd_hzk16 < 0)
{
printf("can't open HZK16\n");
return -1;
}
//获得文件的状态信息,里面含有文件长度,这在后面的 mmap 中用到。
if(fstat(fd_hzk16, &hzk_stat))
{
printf("can't get fstat\n");
return -1;
}
//使用 mmap 映射文件,以后就可以像访问内存一样读取文件内容;mmap 的返回结果保存在 hzkmem 中,它将作为字库的基地址。
hzkmem = (unsigned char *)mmap(NULL , hzk_stat.st_size, PROT_READ, MAP_SHARED, fd_hzk16, 0);
if (hzkmem == (unsigned char *)-1)
{
printf("can't mmap for hzk16\n");
return -1;
}
/* 清屏: 全部设为黑色 */
memset(fbmem, 0, screen_size);
lcd_put_ascii(var.xres/2, var.yres/2, 'A'); /*在屏幕中间显示8*16的字母A*/
printf("chinese code: %02x %02x\n", str[0], str[1]);
lcd_put_chinese(var.xres/2 + 8, var.yres/2, str);
munmap(fbmem , screen_size);
close(fd_fb);
return 0;
}
交叉编译后将可执行文件和汉字库HZK16复制到开发板
交叉编译 freetype 矢量库
使用 buildroot 来给 ARM 板编译程序、编译库会很简单
程序运行的一些基础知识
-
编译程序时去哪找头文件?
系统目录:就是交叉编译工具链里的某个 include 目录;也可以自己指定: -
链接时去哪找库文件 ?
系统目录:就是交叉编译工具链里的某个 lib 目录;也可以自己指定:链 -
运行时去哪找库文件 ?
系统目录:就是板子上的/lib、/usr/lib 目录;也可以自己指定:运行
程序用环境变量 LD_LIBRARY_PATH 指定。 -
运行时不需要头文件,所以头文件不用放到板子上
怎么确定“系统目录”?
执行下面命令确定目录:
echo 'main(){}'| arm-buildroot-linux-gnueabihf-gcc -E -v -
它会列出头文件目录、库目录(LIBRARY_PATH)。
你需要在头文件目录中确定有没有这个文件,或是自己指定头文件目录。
交叉编译程序的万能命令
如果交叉编辑工具链的前缀是 arm-buildroot-linux-gnueabihf- ,比如 arm-buildroot-linux-gnueabihf-gcc ,交叉编译开源软件时,如果它里面有 configure
,
万能命令如下:
./configure --host=arm-buildroot-linux-gnueabihf --prefix=$PWD/tmp
make
make install
就可以在当前目录的 tmp 目录下看见 bin, lib, include 等目录,里面存有可执行程序、库、头文件。
执行如下指令 ,可以查看configure后面能够携带哪些参数
,/configure --help
交叉编译freetype的依赖库
下载freetype:版本2.10.2
编译的时候提示需要安装这些依赖库才能正常编译freetype:
有些交叉编译工具链包含该zlib库,则不需要再手动操作,如果该编译工具链不包含,则需要编译,安装,并复制到系统目录的lib和include目录下
例如:
cp include/* -rf /home/book/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot/usr/include
cp lib/* -rfd /home/book/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot/usr/lib
-d表示将lib库里面的链接关系也复制过去
【所谓的编译工具链包含该库指的就是编译工具链的系统目录下包含该头文件和库,这样在编译时就可以找到所需的库与文件
】
注意:make编译的时候可能会找到/usr/local/lib里面的zlib和libpng库,所以要保持这两个库的版本和编译工具链的系统目录一致
使用 freetype 显示一个文字
wchar_t 宽字符
#include <stdio.h>
#include <string.h>
#include <wchar.h>
int main( int argc, char** argv)
{
wchar_t *chinese_str = L"中gif";
unsigned int *p = (wchar_t *)chinese_str;
int i;
printf("sizeof(wchar_t) = %d, str's Uniocde: \n", (int)sizeof(wchar_t));
for (i = 0; i < wcslen(chinese_str); i++)//wcslen:获取字符串长度,用于宽字符集。
{
printf("0x%x ", p[i]);
}
printf("\n");
return 0;
}
输出结果
每个wchar_t类型的字符占四个字节
Freetype库简介
采用了点阵字库来显示字母、汉字等等,由于对这些字体进行放大或者缩小的话,会使字体在显示时可能会出现模糊或者锯齿,故引用了矢量字体(放大缩小不会失真):
矢量字体的形成:
- ①:确定关键点
- ②:使用数学曲线(贝塞尔曲线)连接关键点
- ③:填充闭合曲线内部空间
simsun.ttc文件中,开头是一些字符映射表,以适应不同的编码格式。
不同的编码通过自己的字符映射表找到当前这个文字对应的点阵数据,称为glyph
。
有了以上基础,一个文字的显示过程可以概括如下:=
- ①·给定一个字符可以确定它的编码值(ASCII、
UNICODE
、GB2312); - ②·设置字体大;
- ③·根据编码值,从文件头部中通过charmap找到对应的关键点(glyph),它会根据字体大小调整关键点;
- ④·把关键点转换为位图点阵;
- ⑤·在LCD上显示出来
Freetype的使用:
-
初始化:FT_InitFreetype
-
加载(打开)字体Face:FT_New_Face
-
设置字体大小:FT_Set_Char_Sizes 或FT_Set_Pixel_Sizes
-
选择Charmp:FT_Select_Charmap,字符映射表
-
根据编码值charcode找到glyph_index:glyph_index = FT_Get_Char_Index(face,charcode)
-
根据glyph_index取出glyph:FT_Load_Glyph(face,glyph_index)
-
转为位图:FT_Render_Glyph
-
移动或旋转:FT_Set_Transform
-
最后显示出来
上面的⑤⑥⑦可以使用一个函数代替:FT_Load_Char(face, charcode, FT_LOAD_RENDER),它就可以根据关键点得到位图。
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/fb.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <wchar.h>
#include <sys/ioctl.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
int fd_fb;
struct fb_var_screeninfo var; /* Current var */
int screen_size;
unsigned char *fbmem;
unsigned int line_width;
unsigned int pixel_width;
/**********************************************************************
* 函数名称: lcd_put_pixel
* 功能描述: 在LCD指定位置上输出指定颜色(描点)
* 输入参数: x坐标,y坐标,颜色
* 输出参数: 无
* 返 回 值: 会
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2020/05/12 V1.0 zh(angenao) 创建
***********************************************************************/
void lcd_put_pixel(int x, int y, unsigned int color)
{
unsigned char *pen_8 = fbmem+y*line_width+x*pixel_width;
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;
break;
}
case 16:
{
/* 565 */
red = (color >> 16) & 0xff;
green = (color >> 8) & 0xff;
blue = (color >> 0) & 0xff;
color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
*pen_16 = color;
break;
}
case 32:
{
*pen_32 = color;
break;
}
default:
{
printf("can't surport %dbpp\n", var.bits_per_pixel);
break;
}
}
}
/**********************************************************************
* 函数名称: draw_bitmap
* 功能描述: 根据bitmap位图,在LCD指定位置显示汉字
* 输入参数: x坐标,y坐标,位图指针
* 输出参数: 无
* 返 回 值: 无
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2020/05/12 V1.0 zh(angenao) 创建
***********************************************************************/
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;
FT_Int y_max = y + bitmap->rows;
//printf("x = %d, y = %d\n", x, y);
for ( j = y, q = 0; j < y_max; j++, q++ )
{
for ( i = x, p = 0; i < x_max; i++, p++ )
{
if ( i < 0 || j < 0 ||
i >= var.xres || j >= var.yres )
continue;
//image[j][i] |= bitmap->buffer[q * bitmap->width + p];
lcd_put_pixel(i, j, bitmap->buffer[q * bitmap->width + p]);//在位图里面每个像素只有一个字节,不是rgba的四个字节
}
}
}
int main(int argc, char **argv)
{
//wchar_t:是C/C++的字符类型,是一种扩展的存储方式,一般为16位或32位。
wchar_t *chinese_str = L"繁";
FT_Library library;
FT_Face face;
int error;
FT_Vector pen;
FT_GlyphSlot slot;
int font_size = 24;
if (argc < 2)
{
printf("Usage : %s <font_file> [font_size]\n", argv[0]);
return -1;
}
if (argc == 3)
font_size = strtoul(argv[2], NULL, 0);
fd_fb = open("/dev/fb0", O_RDWR);
if (fd_fb < 0)
{
printf("can't open /dev/fb0\n");
return -1;
}
//使用 ioctl 获得 LCD 分辨率、BPP,存储在var
if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
{
printf("can't get var\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;
//fb_base :根据内核申请到的在用户空间态可以使用的基地址
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;
}
/* 清屏: 全部设为黑色 */
memset(fbmem, 0, screen_size);
/* 显示矢量字体 */
//初始化
error = FT_Init_FreeType( &library ); /* initialize library */
/* error handling omitted */
//加载(打开)字体Face
error = FT_New_Face( library, argv[1], 0, &face ); /* create face object */
/* error handling omitted */
slot = face->glyph;
FT_Set_Pixel_Sizes(face, font_size, 0);//设置字体大小
/* 确定座标:
*/
//pen.x = 0;
//pen.y = 0;
/* set transformation */
//FT_Set_Transform( face, 0, &pen);
/* load glyph image into the slot (erase previous one) */
error = FT_Load_Char( face, chinese_str[0], FT_LOAD_RENDER );//得到位图
if (error)
{
printf("FT_Load_Char error\n");
return -1;
}
draw_bitmap( &slot->bitmap,
var.xres/2,
var.yres/2);
return 0;
}
bitmap结构体
交叉编译:
arm-buildroot-linux-gnueabihf-gcc -o lcd_freetype_font lcd_freetype_font.c -lfreetype //编译时要连接freetype库
移植交叉编译后的可执行文件和文字库(simsun.ttc)到开发板
执行:
./freetype_show_font simsun.ttc 200 //运行时,第三个参数可选,为字体大小
参考博文:【硬核】韦东山:使用freetype显示一行文字
在LCD上令矢量字体旋转某个角度
参考博文:使用freetype显示单个文字
需要设置矩阵,设置旋转角度(转化为弧度值)
使用 freetype 显示一行文字
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/fb.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <wchar.h>
#include <sys/ioctl.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_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;
/* color : 0x00RRGGBB */
void lcd_put_pixel(int x, int y, unsigned int color)
{
unsigned char *pen_8 = fbmem+y*line_width+x*pixel_width;
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;
break;
}
case 16:
{
/* 565 */
red = (color >> 16) & 0xff;
green = (color >> 8) & 0xff;
blue = (color >> 0) & 0xff;
color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
*pen_16 = color;
break;
}
case 32:
{
*pen_32 = color;
break;
}
default:
{
printf("can't surport %dbpp\n", var.bits_per_pixel);
break;
}
}
}
/**********************************************************************
* 函数名称: draw_bitmap
* 功能描述: 根据bitmap位图,在LCD指定位置显示汉字
* 输入参数: x坐标,y坐标,位图指针
* 输出参数: 无
* 返 回 值: 无
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2020/05/12 V1.0 zh(angenao) 创建
***********************************************************************/
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;
FT_Int y_max = y + bitmap->rows;
//printf("x = %d, y = %d\n", x, y);
for ( j = y, q = 0; j < y_max; j++, q++ )
{
for ( i = x, p = 0; i < x_max; i++, p++ )
{
if ( i < 0 || j < 0 ||
i >= var.xres || j >= var.yres )
continue;
//image[j][i] |= bitmap->buffer[q * bitmap->width + p];
lcd_put_pixel(i, j, bitmap->buffer[q * bitmap->width + p]);
}
}
}
int compute_string_bbox(FT_Face face, wchar_t *wstr, FT_BBox *abbox)
{
int i;
int error;
FT_BBox bbox;
FT_BBox glyph_bbox;
FT_Vector pen;
FT_Glyph glyph;
FT_GlyphSlot slot = face->glyph;//保存字符的处理结果:比如转换后的glyph、位图
/* 初始化 */
bbox.xMin = bbox.yMin = 32000;
bbox.xMax = bbox.yMax = -32000;
/* 指定原点为(0, 0) */
pen.x = 0;
pen.y = 0;
/* 计算每个字符的bounding box */
/* 先translate, 再load char, 就可以得到它的外框了 */
for (i = 0; i < wcslen(wstr); i++)
{
/* 转换:transformation */
FT_Set_Transform(face, 0, &pen);
/* 加载位图: load glyph image into the slot (erase previous one) */
error = FT_Load_Char(face, wstr[i], FT_LOAD_RENDER);
if (error)
{
printf("FT_Load_Char error\n");
return -1;
}
/* 取出glyph */
error = FT_Get_Glyph(face->glyph, &glyph);
if (error)
{
printf("FT_Get_Glyph error!\n");
return -1;
}
/* 从glyph得到外框: bbox */
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &glyph_bbox);
/* 更新外框 */
if ( glyph_bbox.xMin < bbox.xMin )
bbox.xMin = glyph_bbox.xMin;
if ( glyph_bbox.yMin < bbox.yMin )
bbox.yMin = glyph_bbox.yMin;
if ( glyph_bbox.xMax > bbox.xMax )
bbox.xMax = glyph_bbox.xMax;
if ( glyph_bbox.yMax > bbox.yMax )
bbox.yMax = glyph_bbox.yMax;
/* 计算下一个字符的原点: increment pen position */
pen.x += slot->advance.x;//前一个字符的原点+advance
pen.y += slot->advance.y;
}
/* return string bbox */
*abbox = bbox;
}
int display_string(FT_Face face, wchar_t *wstr, int lcd_x, int lcd_y)
{
int i;
int error;
FT_BBox bbox;
FT_Vector pen;
FT_Glyph glyph;
FT_GlyphSlot slot = face->glyph;//保存字符的处理结果:比如转换后的glyph、位图
/* 把LCD坐标转换为笛卡尔坐标 */
int x = lcd_x;
int y = var.yres - lcd_y;
/* 计算外框 */
compute_string_bbox(face, wstr, &bbox);
/* 反推原点 */
pen.x = (x - bbox.xMin) * 64; /* 单位: 1/64像素 */
pen.y = (y - bbox.yMax) * 64; /* 单位: 1/64像素 */
/* 处理每个字符 */
for (i = 0; i < wcslen(wstr); i++)
{
/* 转换:transformation */
FT_Set_Transform(face, 0, &pen);
/* 加载位图: load glyph image into the slot (erase previous one) */
error = FT_Load_Char(face, wstr[i], FT_LOAD_RENDER);
if (error)
{
printf("FT_Load_Char error\n");
return -1;
}
/* 在LCD上绘制: 使用LCD坐标 */
draw_bitmap( &slot->bitmap,
slot->bitmap_left,
var.yres - slot->bitmap_top);
/* 计算下一个字符的原点: increment pen position */
pen.x += slot->advance.x;
pen.y += slot->advance.y;
}
return 0;
}
int main(int argc, char **argv)
{
wchar_t *wstr = L"百问网www.100ask.net";
FT_Library library;
FT_Face face;
int error;
FT_BBox bbox;
int font_size = 24;
int lcd_x, lcd_y;
if (argc < 4)
{
printf("Usage : %s <font_file> <lcd_x> <lcd_y> [font_size]\n", argv[0]);
return -1;
}
lcd_x = strtoul(argv[2], NULL, 0);
lcd_y = strtoul(argv[3], NULL, 0);
if (argc == 5)
font_size = strtoul(argv[4], NULL, 0);
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;
}
/* 清屏: 全部设为黑色 */
memset(fbmem, 0, screen_size);
error = FT_Init_FreeType( &library ); /* initialize library */
error = FT_New_Face( library, argv[1], 0, &face ); /* create face object */
FT_Set_Pixel_Sizes(face, font_size, 0);
display_string(face, wstr, lcd_x, lcd_y);
return 0;
}
注意:
-
笛卡尔坐标系与LCD屏幕的坐标系区别
-
freetype设定了每个字符的大小,设计每个字符的原点
参考博文:【硬核】韦东山:使用freetype显示一行文字