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 板编译程序、编译库会很简单

程序运行的一些基础知识

  1. 编译程序时去哪找头文件?
    系统目录:就是交叉编译工具链里的某个 include 目录;也可以自己指定:

  2. 链接时去哪找库文件 ?
    系统目录:就是交叉编译工具链里的某个 lib 目录;也可以自己指定:链

  3. 运行时去哪找库文件 ?
    系统目录:就是板子上的/lib、/usr/lib 目录;也可以自己指定:运行
    程序用环境变量 LD_LIBRARY_PATH 指定。

  4. 运行时不需要头文件,所以头文件不用放到板子上

怎么确定“系统目录”?

执行下面命令确定目录:

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的使用:

  1. 初始化:FT_InitFreetype

  2. 加载(打开)字体Face:FT_New_Face

  3. 设置字体大小:FT_Set_Char_Sizes 或FT_Set_Pixel_Sizes

  4. 选择Charmp:FT_Select_Charmap,字符映射表

  5. 根据编码值charcode找到glyph_index:glyph_index = FT_Get_Char_Index(face,charcode)

  6. 根据glyph_index取出glyph:FT_Load_Glyph(face,glyph_index)

  7. 转为位图:FT_Render_Glyph

  8. 移动或旋转:FT_Set_Transform

  9. 最后显示出来
    上面的⑤⑥⑦可以使用一个函数代替: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显示一行文字

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FreeType是一个开源的字体渲染,它提供了一套功能强大的API,用于加载、解析和渲染字体文件。使用FreeType,您可以在应用程序中实现高质量的字体渲染。 以下是使用FreeType加载和渲染字体的基本步骤: 1. 下载和安装FreeType:您可以从FreeType官方网站(https://www.freetype.org)下载文件,并按照文档进行安装。 2. 引入头文件和链接:在您的项目中引入FreeType的头文件和链接。具体的引入方式会根据您使用的编程语言和开发环境而有所不同。 3. 初始化FreeType:在使用FreeType之前,需要调用FT_Init_FreeType函数来初始化FreeType。 4. 加载字体文件:使用FT_New_Face函数加载字体文件。您需要提供字体文件的路径,并指定要加载的字体索引(如果字体文件包多个字体)。 5. 设置字体大小:使用FT_Set_Pixel_Sizes函数或FT_Set_Char_Size函数设置字体的大小。 6. 渲染字符:使用FT_Load_Char函数加载要渲染的字符,并使用FT_Render_Glyph函数将字符渲染为位图。 7. 获取位图数据:通过FT_GlyphSlot结构体中的bitmap成员获取位图数据。 8. 绘制位图:将位图数据绘制到屏幕或纹理上,以实现字体渲染效果。 9. 释放资源:在使用完FreeType后,需要调用相应的清理函数来释放资源,例如FT_Done_Face和FT_Done_FreeType。 请注意,以上仅为使用FreeType的基本步骤,具体的实现方式会根据您的需求和编程语言而有所不同。您可以参考FreeType的文档和示例代码,以及相关编程语言的FreeType绑定或封装的使用指南,来更详细地了解和应用FreeType。 希望这些信息对您有所帮助!如果您有任何进一步的问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值