【嵌入式Linux】Framebuffer应用编程及字符显示嵌入式Linux应用开发基础知识
前言
卫东山嵌入式Linux应用开发基础知识学习笔记
视频教程地址:https://www.bilibili.com/video/BV1kk4y117Tu
1.帧缓冲应用程序编程
在 Linux 系统中,LCD 由 Framebuffer 驱动程序控制。 Frame表示帧,buffer表示缓冲区,表示Framebuffer是一块内存,其中存储一帧图像。 Framebuffer 存储一帧图像的每个像素的颜色值。假设 LCD 的分辨率为 1024x768,每个像素的颜色用 32 位表示,那么 Framebuffer 的大小为:1024x768x32/8u003d3145728 字节。
简单介绍一下LCD的工作原理:
1 通过驱动设置 LCD 控制器:
根据LCD的参数设置LCD控制器的时序和信号极性;
根据 LCD 分辨率和 BPP 分配 Framebuffer。
2 APP使用ioctl获取LCD分辨率和BPP
3 APP通过mmap映射Framebuffer,在Framebuffer中写入数据
1.1 Framebuffer中像素颜色值起始地址的计算
▲ LCD 和 Framebuffer
▲ (x,y) 像素地址计算
(x,y)_offset u003d (y * x_res + x) * bpp / 8
(x,y) 起始地址 u003d fb_base + (y * x_res + x) * bpp / 8
1.2.像素颜色如何以二进制表示?
▲RGB888 RGB565 RGB555
对于 32BPP,一般只设置低 24 位,高 8 位表示透明度,一般 LCD 不支持。
对于24BPP,为了便于硬件处理,在Framebuffer中也用32位表示,效果与32BPP相同。
对于16BPP,常用RGB565; RGB555很少使用,可以通过ioctl读取驱动中的RGB位偏移量来确定使用哪种格式。
(x,y) 像素地址计算也可以表示为: fb_base + y * line_width + x * pixel_width 用下面的程序表示
其中行_width u003d xres * bpp / 8 ,像素_width u003d bpp / 8
▲ (x,y) 像素地址计算
1.3 程序解析
! zoz100037](https://programming.vip/images/doc/721047ba8eec0f40f8cdcb8bffe965d0.jpg)
▲ 屏幕变量参数结构
主程序
static int fd_fb;//设备节点
静态结构 fb_var_screeninfo 变量; /* Current var */ //存储屏幕变量参数
static int screen_size;//屏幕内存大小字节
static unsigned char *fb_base;//像素基地址
static unsigned int line_width;//行宽字节
static unsigned int pixel_width;//像素宽度字节
int main(int argc, char **argv)
{
诠释 i, j;
fd_fb u003d open("/dev/fb0", O_RDWR);
如果 (fd_fb < 0)
{
printf("无法打开 /dev/fb0\n");
返回-1;
}
if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))//FrameBufferIOGET_ValueSCREENINFOmation
{
printf("无法获取变量\n");
返回-1;
}
线_width u003d var.xres * var.bits_per_pixel / 8;
像素_width u003d var.bits_per_pixel / 8;
屏幕_size u003d var.xres * var.yres * var.bits_per_pixel / 8;
fb_base u003d (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
if (fb_base u003du003d (unsigned char *)-1)
{
printf("不能映射\n");
返回-1;
}
/* 清屏:全部设置为白色 */
memset(fb_base, 0xff, 屏幕_size);
/* 随机设置 100 * 100 为红色 */
for (j u003d 0; j < 100; j++)
{
for (i u003d 0; i < 100; i++)
lcd_put_pixel(var.xres/2+i, var.yres/2+j, 0xFF0000);//传入的颜色格式为RGB888
}
munmap(fb_base , screen_size);//取消FB_base的内存地址映射
关闭(fd_fb);
返回 0;
}
void lcd_put_pixel(int x, int y, unsigned int color)
//RGB888 -> RGB565
void lcd_put_pixel(int x, int y, unsigned int color)
{
unsigned char *pen_8 u003d fb_base+y*line_width+x*pixel_width;//上面提到的地址算法
无符号短 *pen_16;
无符号整数 *pen_32;
无符号整数红、绿、蓝;
笔_16 u003d(无符号短*)笔_8;
笔_32 u003d(无符号整数*)笔_8;
开关 (bar.bits\per\pixel)
{
案例 8:
{
*笔_8 u003d 颜色;
休息;
}
case 16://bpp u003d 16,即RGB565
{
/* 565 */
//将888数据的高5位、高6位、高5位存入565
红色 u003d (颜色 >> 16) & 0xff;
绿色 u003d (颜色 >> 8) & 0xff;
蓝色 u003d (颜色 >> 0) & 0xff;
颜色 u003d ((红色 >> 3) << 11) | ((绿色 >> 2) << 5) | (蓝色 >> 3);
*笔_16 u003d 颜色;
休息;
}
案例 32:
{
*笔_32 u003d 颜色;
休息;
}
默认:
{
printf("不能支持 %dbpp\n", var.bits_per_pixel);
休息;
}
}
}
2.文字和字符显示
2.1 字符编码方法
▲ 字符的编码方法
▲ UTF-8 变长码“中”
2.2.实现字符串在LCD上的显示
2.1 实现原理
要在LCD上显示一个ASCII字符,也就是英文字母,这些字符,首先要找到字符对应的点阵。 Linux内核源码中有这个文件:lib\fonts\font_8x16.c。每个字符的点阵以数组的形式保存
▲ 字符的点阵数据
数组中的数字如何表示晶格?以字符 A 为例,如下图所示:
▲ 数组中的数字如何代表格子
2.2.2 程序解析
/*省略字体数据_8x16字符数组,包含ASCII字符对应的点阵数据*/
...
int fd_fb;
结构 fb_var_screeninfo 变量; /* 当前变量 */
int 屏幕_size;
无符号字符 *fbmem;
无符号整数线_宽度;
无符号整数像素_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)
{
无符号字符 *pen_8 u003d fbmem+y*line_width+x*pixel_width;
无符号短 *pen_16;
无符号整数 *pen_32;
无符号整数红、绿、蓝;
笔_16 u003d(无符号短*)笔_8;
笔_32 u003d(无符号整数*)笔_8;
开关 (bar.bits\per\pixel)
{
案例 8:
{
*笔_8 u003d 颜色;
休息;
}
案例 16:
{
/* 565 */
红色 u003d (颜色 >> 16) & 0xff;
绿色 u003d (颜色 >> 8) & 0xff;
蓝色 u003d (颜色 >> 0) & 0xff;
颜色 u003d ((红色 >> 3) << 11) | ((绿色 >> 2) << 5) | (蓝色 >> 3);
*笔_16 u003d 颜色;
休息;
}
案例 32:
{
*笔_32 u003d 颜色;
休息;
}
默认:
{
printf("不能支持 %dbpp\n", var.bits_per_pixel);
休息;
}
}
}
/************************ ************************* *********************
* 函数名:lcd_put_ascii
* 功能说明:在LCD指定位置显示一个8个* 16字符
* 输入参数:x坐标、y坐标、ascii码、RGB888颜色值
* 输出参数:无
* 返回值:无
* 修改日期 修改内容修改的版本号
* ------------------------------------------------------------
* 2022/02/15 V1.1 Joseph 入口参数增加RGB888颜色值和颜色逻辑
************************\ *************************\ **********************/
void lcd_put_ascii(int x, int y, unsigned char c, int color)
{
无符号字符 *dots u003d (无符号字符 *)&fontdata_8x16[c*16];
int i, b;
无符号字符字节;
for (i u003d 0; i < 16; i++)
{
字节 u003d 点[i];
for (b u003d 7; b >u003d 0; b--)
{
if (字节 & (1<<b))
{
/* 显示*/
lcd_put_pixel(x+7-b, y+i, 颜色); /* 白色的 */
}
其他
{
/* 隐藏 */
lcd_put_pixel(x+7-b, y+i, 0); /* 黑色的 */
}
}
}
}
/************************ ************************* *********************
* 函数名:lcd_show_string
* 功能说明:在LCD指定位置显示字符串
* 输入参数:x坐标、y坐标、ascii码、RGB888颜色值
* 输出参数:无
* 返回值:无
* 修改日期 修改内容修改的版本号
* ------------------------------------------------------------
* 2022/02/15 V1.0 约瑟夫建立
************************\ *************************\ **********************/
void lcd_show_string(int x, int y, unsigned char *string, int color)
{
int 字符串_len u003d strlen(字符串);
整数字符串_x u003d x;
整数字符串_y u003d y;
for(int i u003d 0; i < string_len; i++)
{
如果(字符串[i] u003du003d '\n')
{
字符串_y +u003d 18;
字符串_x u003d 0;
如果(字符串_y > var.yres)
{
返回;
}
}
其他
{
lcd_put_ascii(string_x, string_y, string[i], 颜色);
字符串_x +u003d 8;
如果(字符串_x > var.xres)
{
字符串_x u003d 0;
字符串_y +u003d 18;
如果(字符串_y > var.yres)
{
返回;
}
}
}
}
}
int main(int argc, char **argv)
{
fd_fb u003d open("/dev/fb0", O_RDWR);
如果 (fd_fb < 0)
{
printf("无法打开 /dev/fb0\n");
返回-1;
}
if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
{
printf("无法获取变量\n");
返回-1;
}
线_width u003d var.xres * var.bits_per_pixel / 8;
像素_width u003d var.bits_per_pixel / 8;
屏幕_size u003d var.xres * var.yres * var.bits_per_pixel / 8;
fbmem u003d (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
if (fbmem u003du003d (unsigned char *)-1)
{
printf("不能映射\n");
返回-1;
}
/* 清屏:全部设置为黑色 */
memset(fbmem, 0, 屏幕_size);
lcd_show_string(var.xres/2, var.yres/2, "Hello world!\nHello Linux!\n", 0xff0000);
munmap(fbmem,屏幕_size);
关闭(fd_fb);
返回 0;
}
2.3点阵汉字显示
2.3.1 显示原理
和显示ASCII字符的原理一样,显示汉字就是通过字符的编码值从汉字的点阵数据库中找到点阵数据,然后显示在屏幕上
此处使用 HZK16 文件。本文件为常用汉字的16*16点阵字库。 HZK16中每个汉字用32个字节来描述,如下图所示:
▲ 汉字库数据显示方式
这里需要注意的是,这个字体库只能匹配ANSI编码格式下GB2312字符集格式的字符,也就是说我们在写源码的时候,需要用同样的格式输入汉字才能找到点字体库中的矩阵数据。我们可以直接使用这种格式来编写源代码,也可以在编译前添加-fexec charset u003d GB2312将源文件转换为GB2312。例如,使用 arm builderoot Linux gnueabihf GCC - fexec charset u003d GB2312 - o show_ chinese show_ chinese。 C编译程序
2.3.2 程序解析
主功能
int main(int argc, char **argv)
{
无符号字符 str[] u003d "in";
fd_fb u003d open("/dev/fb0", O_RDWR);
如果 (fd_fb < 0)
{
printf("无法打开 /dev/fb0\n");
返回-1;
}
if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
{
printf("无法获取变量\n");
返回-1;
}
线_width u003d var.xres * var.bits_per_pixel / 8;
像素_width u003d var.bits_per_pixel / 8;
屏幕_size u003d var.xres * var.yres * var.bits_per_pixel / 8;
fbmem u003d (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
if (fbmem u003du003d (unsigned char *)-1)
{
printf("不能映射\n");
返回-1;
}
fd_hzk16 u003d open("HZK16", O_RDONLY);
如果 (fd_hzk16 < 0)
{
printf("无法打开 HZK16\n");
返回-1;
}
if(fstat(fd_hzk16, &hzk_stat))
{
printf("无法获取 fstat\n");
返回-1;
}
//将fd_hzk16映射到内存并返回起始地址给hzkmem
hzkmem u003d (unsigned char *)mmap(NULL , hzk_stat.st_size, PROT_READ, MAP_SHARED, fd_hzk16, 0);
if (hzkmem u003du003d (unsigned char *)-1)
{
printf("不能为 hzk16 映射\n");
返回-1;
}
/* 清屏:全部设置为黑色 */
memset(fbmem, 0, 屏幕_size);
lcd_put_ascii(var.xres/2, var.yres/2, 'A', 0xff0000); /*8*16的字母A显示在屏幕中间*/
printf("中文代码:%02x %02x\n", str[0], str[1]);
//显示汉字
lcd_put_chinese(var.xres/2 + 8, var.yres/2, str, 0xff0000);
munmap(fbmem,屏幕_size);
关闭(fd_fb);
返回 0;
}
lcd_put_chinese() 函数
void lcd_put_chinese(int x, int y, unsigned char *str, unsigned int color)
{
无符号整数区域 u003d str[0] - 0xA1;
无符号整数其中 u003d str[1] - 0xA1;
无符号字符 *dots u003d hzkmem + (面积 * 94 + where)*32;
无符号字符字节;
int i, j, b;
for (i u003d 0; i < 16; i++)
for (j u003d 0; j < 2; j++)
{
字节 u003d 点[i*2 + j];
for (b u003d 7; b >u003d0; b--)
{
if (字节 & (1<<b))
{
/* 显示*/
lcd_put_pixel(x+j*8+7-b, y+i, 颜色); /* 白色的 */
}
其他
{
/* 隐藏 */
lcd_put_pixel(x+j*8+7-b, y+i, 0); /* 黑色的 */
}
}
}
}
2.4编译freetype/显示汉字遇到的问题
freetype原理及详细教程见嵌入式Linux应用开发V4.0全系列视频文档-STM32MP157开发板完整手册
解决这个问题:
libtool:警告:库'/home/book/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/.../arm-buildroot-linux-gnueabihf/sysroot/usr /lib/libharfbuzz.la' 被移动。
/bin/grep: /home/book/stm32mp157/ST-Buildroot/output/host/bin/.../arm-buildroot-linux-gnueabihf/sysroot/usr/lib/libfreetype.la: 没有这样的文件或目录
/bin/sed:无法读取 /home/book/stm32mp157/ST-Buildroot/output/host/bin/.../arm-buildroot-linux-gnueabihf/sysroot/usr/lib/libfreetype.la:没有文件或目录
libtool:错误:'/home/book/stm32mp157/ST-Buildroot/output/host/bin/.../arm-buildroot-linux-gnueabihf/sysroot/usr/lib/libfreetype.la' 不是有效的 libtool 存档
config.mk:55:目标“/home/book/free/freetype-2.10.2/objs/libfreetype.la”的配方失败
制作:*** [/home/book/free/freetype-2.10.2/objs/libfreetype.la] 错误1
make执行时搜索到的路径/home/book/stm32mp157/st buildroot不存在
修改路径的文件:
.../100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot/usr/lib/libfreetype.la
.../100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot/usr/lib/libharfbuzz.la
如有错误请自行处理
▲ 待修改文件
部分程序解析
主功能
int main(int argc, char **argv)
{
wchar_t *中文_str u003d L“乘”;
FT_库库;
FT_Face face;
内部错误;
FT_矢量笔;
FT_GlyphSlot 插槽;
int 字体_size u003d 24;
如果(argc < 2)
{
printf("用法:%s <font\file> [font\size]\on", argv[0]);
返回-1;
}
如果(argc u003du003d 3)
字体_size u003d strtoul(argv[2], NULL, 0);
fd_fb u003d open("/dev/fb0", O_RDWR);
如果 (fd_fb < 0)
{
printf("无法打开 /dev/fb0\n");
返回-1;
}
if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
{
printf("无法获取变量\n");
返回-1;
}
线_width u003d var.xres * var.bits_per_pixel / 8;
像素_width u003d var.bits_per_pixel / 8;
屏幕_size u003d var.xres * var.yres * var.bits_per_pixel / 8;
fbmem u003d (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
if (fbmem u003du003d (unsigned char *)-1)
{
printf("不能映射\n");
返回-1;
}
/* 清屏:全部设置为黑色 */
memset(fbmem, 0, 屏幕_size);
/* 显示矢量字体*/
//初始化位图
错误 u003d FT_Init_FreeType( &library ); /* 初始化库*/
/* 错误处理省略*/
//加载字体文件并保存在&face
错误 u003d FT_New_Face(库,argv[1],0,&face); /* 创建人脸对象 */
/* 错误处理省略*/
//从face_Glyph Slot中获取ft,文本位图保存在Fort_N glyph slot
槽u003d面->字形;
//设置字体大小
FT_Set_Pixel_Sizes(face, font_size, 0);
/* 将字形图像加载到槽中(擦除前一个)*/
//根据编码值获取位图
/*
FT_Load_Char 可以实现以下三个功能
一个。根据编码值_索引获取字形:FT_Get_Char_Index
乙。根据glyph_idex取出glyph:FT_Load_Glyph
c.渲染位图:FT_Render_Glyph
*/
错误 u003d FT_Load_Char(face, chinese_str[0], FT_LOAD_RENDER);
//执行ft_load_char后,字符位图存入槽->位图,即面->字形->位图
如果(错误)
{
printf("FT_Load_Char 错误\n");
返回-1;
}
绘制_bitmap( &slot->bitmap,
var.xres/2,
var.yres/2);
返回 0;
}
▲ 位图中的数据格式
绘制_bitmap() 函数
void draw_bitmap( FT_Bitmap* 位图,
FT_Int x,
FT_Int y)
{
FT_Int i, j, p, q;
FT_Int x_max u003d x + 位图->宽度;
FT_Int y_max u003d y + 位图->行;
//printf("x u003d %d, y u003d %d\n", x, y);
for ( j u003d y, q u003d 0; j < y_max; j++, q++ )
{
for ( i u003d x, p u003d 0; i < x_max; i++, p++ )
{
如果 ( i < 0 || j < 0 ||
我 >u003d var.xres || j >u003d var.yres )
继续;
//图像[j][i] |u003d bitmap->buffer[q * bitmap->width + p];
lcd_put_pixel(i, j, bitmap->buffer[q * bitmap->width + p]);
}
}
}
2.7 汉字旋转
部分程序解析
int main(int argc, char **argv)
{
wchar_t *中文_str u003d L“乘”;
FT_库库;
FT_Face face; //矢量字体的外观对象,用于保存字体外观个数、当前外观索引、当前外观包含的字体文件个数等相关数据,
//外观可以简单理解为常规、斜体、粗体等字体样式
内部错误;
FT_矢量笔; //字符的来源
FT_GlyphSlot 插槽; //字形槽:一次只能存储一个字形图像。每个面对象都有一个字形槽,位于面->字形
int 字体_size u003d 24;
FT_Matrix 矩阵; /* 变换矩阵 */
双角;
如果(argc < 3)
{
printf("用法:%s <font_file> <angle> [font_size]\n", argv[0]);
返回-1;
}
//弧度旋转角度
角度 u003d ( 1.0* strtoul(argv[2], NULL, 0) / 360 ) * 3.14159 * 2; /* 使用 25 度 */
如果(argc u003du003d 4)
字体_size u003d strtoul(argv[3], NULL, 0);
fd_fb u003d open("/dev/fb0", O_RDWR);
如果 (fd_fb < 0)
{
printf("无法打开 /dev/fb0\n");
返回-1;
}
if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
{
printf("无法获取变量\n");
返回-1;
}
线_width u003d var.xres * var.bits_per_pixel / 8;
像素_width u003d var.bits_per_pixel / 8;
屏幕_size u003d var.xres * var.yres * var.bits_per_pixel / 8;
fbmem u003d (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
if (fbmem u003du003d (unsigned char *)-1)
{
printf("不能映射\n");
返回-1;
}
/* 清屏:全部设置为黑色 */
memset(fbmem, 0, 屏幕_size);
/* 显示矢量字体*/
错误 u003d FT_Init_FreeType( &library ); /* 初始化库*/
/* 错误处理省略*/
错误 u003d FT_New_Face(库,argv[1],0,&face); /* 创建人脸对象 */
/* 错误处理省略*/
槽u003d面->字形;
FT_Set_Pixel_Sizes(face, font_size, 0);
/* 确定坐标:
*/
笔.x u003d 0;
笔.y u003d 0;
/* 设置矩阵*/
//设置矩阵
matrix.xx u003d (FT_Fixed)( cos( 角度 ) * 0x10000L );
matrix.xy u003d (FT_Fixed)(-sin(角度) * 0x10000L);
matrix.yx u003d (FT_Fixed)( sin( 角度 ) * 0x10000L );
matrix.yy u003d (FT_Fixed)( cos( 角度 ) * 0x10000L );
/* 设置变换*/
//变形
FT_Set_Transform(面,&matrix,&pen);
/* 将字形图像加载到槽中(擦除前一个)*/
//获取位图
错误 u003d FT_Load_Char(face, chinese_str[0], FT_LOAD_RENDER);
如果(错误)
{
printf("FT_Load_Char 错误\n");
返回-1;
}
绘制_bitmap( &slot->bitmap,
var.xres/2,
var.yres/2);
返回 0;
}
2.8行汉字
涉及的几个重要概念
▲ LCD坐标系中点坐标与笛卡尔坐标系的关系
▲ 字形索引
基线:基线。文本可以在同一行实现,主要是因为它的起源在基线上
起源:起源。同一行中字符的原点位于同一基线上,这决定了它的 y 坐标。它的 x 坐标是从前一个原点的 x+advance 获得的。需要指定第一个字符的来源
Advance:提前,连接两个原点x坐标的差值
xMin, xMax, yMin, yMax, width, height: (xMin, yMin) - > (xMax, yMax) 表示显示字符需要最小坐标范围,其中width u003d xMax - xMin, height u003d yMax - yMin
BrearingX,bearingY:brearingX u003d xMin - orgin_x,bearingY u003d yMax - orgin_y
如何获得 xMin、xMax、yMin、yMax?
ft可以用_字形_获取_Cbox函数获取一个字体的这些参数,会保存在一个ft_bbox结构中,需要计算外框的时候需要用到这个信息未来的一行文字:
▲FT_BBox_结构形态
有了上面的基础知识,现在如何在指定位置显示一行文字呢?
首先,如何确定这行文本的轮廓:每个字符都有自己的轮廓:xMin、xMax、yMin、yMax。取这些字符的 xMin 和 yMin 的最小值和这些字符的 xMax 和 yMax 的最大值来确定这行文本的轮廓。
那么你可以在指定位置(x,y)显示一行文本:
1首先指定第一个字符的原点笔坐标为(0, 0),计算其外框
2 计算正确字符的原点及其外框
处理完所有字符后,可以通过上面提到的方法得到一行文本的整体轮廓:假设轮廓左上角的坐标为(x', y')。
3如果要在(x,y)处显示这一行文字,可以调整笔坐标,因为我们在这一系列操作中只填写了一个坐标,就是起始文本原点的笔坐标
那么如何调整呢?
当pen为(0, 0)时,对应边框的左上角(x', y');
那么当左上角为(x, y)时,pen可以计算为(x-x', y-y')。
涉及的几个重要数据结构
FT_Library:对应freetype库,使用ft_init_freetype()函数初始化这个类型的一个变量
FT_Face:对应一个矢量字体文本,比如simsong TTC文件,使用ft_new_face()函数来初始化字体文件。该函数需要freetype库文件对应的变量作为成员
FT_GlyphSlot:字形槽,用于存放字符处理的结果,如转换后的字形、位图等。什么是字形?中文保存在glyph->glyph->bitmap_glyphslot类型的变量
▲FT_face包含成员变量的示意图
FT_Glyph:字符的原始关键点信息保存在字体文件中。使用freetype功能,您可以放大、缩小和旋转以获得新的关键点。这些新的关键点保存在槽中(注:位图也保存在槽中)
使用 ft 作为新的关键点_ 字形。您可以使用此代码获取字形:error u003d ft from slot_ Get_ Glyph(slot , &glyph);
FT_BBox:结构定义如下。它表示一个字符的外框,即新字形的外框:
! zoz100076](https://programming.vip/images/doc/32fda582a320d8a0fba6cb97db0881ee.jpg)
▲FT_BBox_结构形态
ft 可以使用_ Glyph_ Get_ CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &bbox );从字形_ Bbox 信息中获取 ft
下面的示例代码使用了上面提到的数据结构和函数
▲ 示例代码
部分程序解析
显示字符串:先设置pen为(0,0)推回原点(x',y'),然后用(pen_x,pen_y)u003d(x,y)-(x',计算pen y')
int display_string(FT_Face face, wchar_t *wstr, int lcd_x, int lcd_y)
int display_string(FT_Face face, wchar_t *wstr, int lcd_x, int lcd_y)
{
诠释我;
内部错误;
FT_BBox bbox;
FT_矢量笔;
FT_字形字形;
FT_GlyphSlot slot u003d face->glyph;
/* 将 LCD 坐标转换为笛卡尔坐标 */
int x u003d 液晶显示器_x;
int y u003d var.yres - lcd_y;
/* 计算框架*/
计算_string_bbox(面,wstr,&bbox);
/* 后退原点 */
//为什么是* 64? FT_设置_变换函数要求输入坐标单位为1 / 64像素
pen.x u003d (x - bbox.xMin) * 64; /* 单位:1 / 64 像素 */
pen.y u003d (y - bbox.yMax) * 64; /* 单位:1 / 64 像素 */
/* 处理每个字符 */
for (i u003d 0; i < wcslen(wstr); i++)
{
/* 变换:变换*/
//将笔中的信息整合到人脸中
FT_Set_Transform(face, 0, &pen);
/* 加载位图:将字形图像加载到槽中(擦除前一个)*/
错误 u003d FT_Load_Char(face, wstr[i], FT_LOAD_RENDER);
如果(错误)
{
printf("FT_Load_Char 错误\n");
返回-1;
}
/* 在 LCD 上绘图:使用 LCD 坐标 */
绘制_bitmap( &slot->bitmap,
插槽->位图_left,
var.yres - 插槽->位图_top);
/* 计算下一个字符的原点:增加笔位*/
pen.x +u003d 插槽->advance.x;
pen.y +u003d slot->advance.y;
}
返回 0;
}
计算一行文本的轮廓:
int compute_ string_ Bbox (ft_face, wchar_t * wstr, ft_bbox * abbox) 函数
int compute_string_bbox(FT_Face face, wchar_t *wstr, FT_BBox *abbox)
{
诠释我;
内部错误;
FT_BBox bbox;
FT_BBox 字形_bbox;
FT_矢量笔;
FT_字形字形;
FT_GlyphSlot slot u003d face->glyph;
/* 初始化*/
bbox.xMin u003d bbox.yMin u003d 32000;
bbox.xMax u003d bbox.yMax u003d -32000;
/* 指定原点为 (0, 0) */
笔.x u003d 0;
笔.y u003d 0;
/* 计算每个字符的边界框 */
/* 先翻译再加载char得到它的外框*/
for (i u003d 0; i < wcslen(wstr); i++)
{
/* 变换:变换*/
FT_Set_Transform(face, 0, &pen);
/* 加载位图:将字形图像加载到槽中(擦除前一个)*/
错误 u003d FT_Load_Char(face, wstr[i], FT_LOAD_RENDER);
如果(错误)
{
printf("FT_Load_Char 错误\n");
返回-1;
}
/* 删除字形 */
错误 u003d FT_Get_Glyph(face->glyph, &glyph);
如果(错误)
{
printf("FT_Get_字形错误!\n");
返回-1;
}
/* 从字形中获取轮廓:bbox */
FT_Glyph_Get_CBox(字形, FT_GLYPH_BBOX_TRUNCATE, &glyph_bbox);
/* 更新大纲 */
if (字形_bbox.xMin < bbox.xMin )
bbox.xMin u003d 字形_bbox.xMin;
如果(字形_bbox.yMin < bbox.yMin)
bbox.yMin u003d 字形_bbox.yMin;
如果(字形_bbox.xMax > bbox.xMax)
bbox.xMax u003d 字形_bbox.xMax;
如果(字形_bbox.yMax > bbox.yMax)
bbox.yMax u003d 字形_bbox.yMax;
/* 计算下一个字符的原点:增加笔位*/
pen.x +u003d 插槽->advance.x;
pen.y +u003d slot->advance.y;
}
/* 返回字符串 bbox */
*abbox u003d bbox;
}
增加旋转角度(特定角度显示文字会略显不全,原因暂时未知)
int display_string(FT_Face face, wchar_t *wstr, int lcd_x, int lcd_y, 双角)
{
诠释我;
内部错误;
FT_BBox bbox;
FT_矢量笔;
FT_字形字形;
FT_GlyphSlot slot u003d face->glyph;
FT_Matrix 矩阵; /* 变换矩阵 */
/* 将 LCD 坐标转换为笛卡尔坐标 */
int x u003d 液晶显示器_x;
int y u003d var.yres - lcd_y;
/* 计算框架*/
计算_string_bbox(面,wstr,&bbox);
/* 后退原点 */
//为什么是* 64? FT_设置_变换函数要求输入坐标单位为1 / 64像素
pen.x u003d (x - bbox.xMin) * 64; /* 单位:1 / 64 像素 */
pen.y u003d (y - bbox.yMax) * 64; /* 单位:1 / 64 像素 */
/* 处理每个字符 */
for (i u003d 0; i < wcslen(wstr); i++)
{
/* 设置矩阵*/
//设置矩阵
matrix.xx u003d (FT_Fixed)( cos( 角度 ) * 0x10000L );
matrix.xy u003d (FT_Fixed)(-sin(角度) * 0x10000L);
matrix.yx u003d (FT_Fixed)( sin( 角度 ) * 0x10000L );
matrix.yy u003d (FT_Fixed)( cos( 角度 ) * 0x10000L );
/* 变换:变换*/
//将笔中的信息整合到人脸中
FT_Set_Transform(面,&matrix,&pen);
/* 加载位图:将字形图像加载到槽中(擦除前一个)*/
错误 u003d FT_Load_Char(face, wstr[i], FT_LOAD_RENDER);
如果(错误)
{
printf("FT_Load_Char 错误\n");
返回-1;
}
/* 在 LCD 上绘图:使用 LCD 坐标 */
绘制_bitmap( &slot->bitmap,
插槽->位图_left,
var.yres - 插槽->位图_top);
/* 计算下一个字符的原点:增加笔位*/
pen.x +u003d 插槽->advance.x;
pen.y +u003d slot->advance.y;
}
返回 0;
}
参考资料
MP157下6.4.4交叉编译freetype时,会提示找不到libfreetype la
FreeTpye库学习笔记:将矢量字体解析成位图
更多推荐
所有评论(0)