Framebuffer基础知识(三十)

1、Framebuffer应用编程

  在Linux系统中通过Framebuffer驱动程序来控制LCD。Frame是帧的意思,buffer是缓冲的意思,这意味着Framebuffer就是一块内存,里面保存着一帧图像。Framebuffer中保存着一帧图像的每一个像素颜色值,假设LCD的分辨率是1024x768,每一个像素的颜色用32位来表示,那么Framebuffer的大小就是:1024x768x32/8=3145728字节。
  简单介绍LCD的操作原理:
① 驱动程序设置好LCD控制器:
  根据LCD的参数设置LCD控制器的时序、信号极性;
  根据LCD分辨率、BPP分配Framebuffer。
② APP使用ioctl获得LCD分辨率、BPP
③ APP通过mmap映射Framebuffer,在Framebuffer中写入数据

1.1、像素颜色值在Framebuffer中的起始地址计算

▲LCD和Framebuffer

▲(x,y)像素地址计算

(x,y)_offset = (y * x_res + x) * bpp / 8
(x,y)起始地址 = 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,在下文的程序中便是如此表示
其中line_width = xres * bpp / 8 , pixel_width = bpp / 8

▲(x,y)像素地址计算

1.3、程序分析

▲屏幕可变参数结构体

main.c

static int fd_fb;//设备节点
static struct fb_var_screeninfo var;	/* Current var */   //存放屏幕可变参数
static int screen_size;//屏幕内存大小 byte
static unsigned char *fb_base;//像素基地址 
static unsigned int line_width;//行宽 byte
static unsigned int pixel_width;//像素宽度 byte

int main(int argc, char **argv)
{
	int i,j;
	
	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))//FrameBufferIOGET_ValueSCREENINFOmation
	{
		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 = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
	if (fb_base == (unsigned char *)-1)
	{
		printf("can't mmap\n");
		return -1;
	}

	/* 清屏: 全部设为白色 */
	memset(fb_base, 0xff, screen_size);

	/* 随便设置出100*100个为红色 */
	for (j = 0; j < 100; j++)
	{
		for (i = 0; i < 100; i++)
			lcd_put_pixel(var.xres/2+i, var.yres/2+j, 0xFF0000);//传入颜色格式为RGB888
	}
	
	munmap(fb_base , screen_size);//取消对fb_base的内存地址映射
	close(fd_fb);
	
	return 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 = fb_base+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://bpp = 16 即 RGB565
		{
			/* 565 */
			//分别将888数据的高5位、高6位和高5位存入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;
		}
	}
}

2、文字及字符显示

2.1、字符的编码方式

▲字符的编码方式

▲UTF-8变长编码“中”

2.2、实现在LCD上显示字符串

2.1、实现原理

  要在LCD中显示一个ASCII字符,即英文字母这些字符,首先是要找到字符对应的点阵。在Linux内核源码中有这个文件:lib\fonts\font_8x16.c,里面以数组形式保存各个字符的点阵

▲字符的点阵数据

  数组里的数字是如何表示点阵的?以字符A为例,如下图所示:

▲数组里的数字是如何表示点阵的

2.2.2、程序分析

/*省略fontdata_8x16字符数组,内容包含ASCII码字符对应点阵数据*/
...
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;
		}
	}
}
/**********************************************************************
 * 函数名称: 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)
{
	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, color); /* 白 */
			}
			else
			{
				/* hide */
				lcd_put_pixel(x+7-b, y+i, 0); /* 黑 */
			}
		}
	}
}
/**********************************************************************
 * 函数名称: lcd_show_string
 * 功能描述: 在LCD指定位置上显示字符串
 * 输入参数: x坐标,y坐标,ascii码,RGB888颜色值
 * 输出参数: 无
 * 返 回 值: 无
 * 修改日期        版本号     修改人	      修改内容
 * -----------------------------------------------
 * 2022/02/15	     V1.0	  Joseph	      创建
 ***********************************************************************/ 
void lcd_show_string(int x, int y, unsigned char *string, int color)
{
	int string_len = strlen(string);
	int string_x = x;
	int string_y = y;


	for(int i = 0; i < string_len; i++)
	{
		if(string[i] == '\n')
		{
			string_y += 18;
			string_x = 0;
			if(string_y > var.yres)
			{
				return ;
			}
		}
		else
		{
			lcd_put_ascii(string_x, string_y, string[i], color);
			string_x += 8;
			if(string_x > var.xres)
			{
				string_x = 0;
				string_y += 18;
				if(string_y > var.yres)
				{
					return ;
				}
			}
		}
		
	}

}


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))
	{
		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_show_string(var.xres/2, var.yres/2, "Hello world!\nHello Linux!\n", 0xff0000);

	munmap(fbmem , screen_size);
	close(fd_fb);
	
	return 0;	
}

2.3、中文字符的点阵显示

2.3.1、显示原理

  和显示ASCII字符的原理一样,显示中文字符也是通过字符的编码值从汉字的点阵数据库中寻找到点阵数据而后显示在屏幕上
  在这里用到了一个HZK16的文件,这个文件是常用汉字的16*16点阵字库HZK16里每个汉字使用32字节来描述,如下图所示:

▲汉字库数据显示方式

  在这里需要注意的是,这个字库只能匹配ANSI编码格式下的GB2312字符集格式的字符,这也就意味着我们在编写源码的时候要使用相同的格式来输入汉字才能在字库里搜寻到点阵数据,我们可以直接使用这种格式来编写源码,也可以在编译的时候在添加-fexec-charset=GB2312将源文件转换成GB2312后再编译,例如使用arm-buildroot-linux-gnueabihf-gcc -fexec-charset=GB2312 -o show_chinese show_chinese.c来编译程序

2.3.2、程序分析

main()函数

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;
	}
	if(fstat(fd_hzk16, &hzk_stat))
	{
		printf("can't get fstat\n");
		return -1;
	}
	//将fd_hzk16映射到内存并将起始地址返回到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', 0xff0000); /*在屏幕中间显示8*16的字母A*/
	
	printf("chinese code: %02x %02x\n", str[0], str[1]);
	//显示汉字
	lcd_put_chinese(var.xres/2 + 8,  var.yres/2, str, 0xff0000);

	munmap(fbmem , screen_size);
	close(fd_fb);
	
	return 0;	
}

lcd_put_chinese()函数

void lcd_put_chinese(int x, int y, unsigned char *str, unsigned int color)
{
	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, color); /* 白 */
				}
				else
				{
					/* hide */
					lcd_put_pixel(x+j*8+7-b, y+i, 0); /* 黑 */
				}	
			}
		}
}

2.4、编译使用freetype遇到的问题/显示汉字

解决问题:

修改一下路径的文件即可:
.../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

如果还有报错请自行酌情处理
 

▲需修改的文件

部分程序分析

main()函数

int main(int argc, char **argv)
{
	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;
	}

	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;
	}

	/* 清屏: 全部设为黑色 */
	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 */	
	//从face中获得FT_GlyphSlot,文字的位图就是保存在FT_GlyphSlot里
	slot = face->glyph;

	//设置字体大小
	FT_Set_Pixel_Sizes(face, font_size, 0);


    /* load glyph image into the slot (erase previous one) */
	//根据编码值得到位图
	/*
		FT_Load_Char可实现以下3个功能
		a. 根据编码值获得glyph_index:FT_Get_Char_Index
		b. 根据glyph_idex取出glyph:FT_Load_Glyph
		c. 渲染出位图:FT_Render_Glyph
	*/
    error = FT_Load_Char( face, chinese_str[0], FT_LOAD_RENDER );
	//执行FT_Load_Char之后,字符的位图被存在slot->bitmap里,即face->glyph->bitmap
	
	if (error)
	{
		printf("FT_Load_Char error\n");
		return -1;
	}
	
    draw_bitmap( &slot->bitmap,
                 var.xres/2,
                 var.yres/2);

	return 0;	
}

▲位图中的数据格式

draw_bitmap()函数

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]);
		}
	}
}

2.7、汉字的旋转

部分程序分析

int main(int argc, char **argv)
{
	wchar_t *chinese_str = L"繁";

	FT_Library	  library;
	FT_Face 	  face;					//矢量字体的外观对象,用于保存字体外观数、当前外观索引、当前外观所包含字形文件数等相关数据,
										//其中外观可简单理解为常规、斜体、加粗等字体风格
	int error;
    FT_Vector     pen;					//字符的原点			
	FT_GlyphSlot  slot;					//字形槽:每次只能存储一个字形图像,每个face对象都有一个字形槽,位于face->glyph
	int font_size = 24;
	FT_Matrix	  matrix;				  /* transformation matrix */
	double		  angle;			

	if (argc < 3)
	{
		printf("Usage : %s <font_file> <angle> [font_size]\n", argv[0]);
		return -1;
	}
	//弧度转角度
	angle  = ( 1.0* strtoul(argv[2], NULL, 0) / 360 ) * 3.14159 * 2;	   /* use 25 degrees	 */

	if (argc == 4)
		font_size = strtoul(argv[3], 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;
	}

	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 handling omitted */
	
	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 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 );

    /* set transformation */
	//变形
    FT_Set_Transform( face, &matrix, &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;	
}

2.8、汉字成行

涉及的几个重要的概念

▲LCD坐标系和笛卡尔坐标系中点坐标的关系

▲字形指标

baseline:基线,文字能够实现在看起来在同一行主要是因为其origin都位于基线上
origin:原点,同一行的字符的origin都位于同一条基线上,这就决定了其y坐标,其x坐标是由上一个原点的x+advance得来,第一个字符的原点需要指定
advance:推进,相连两个原点x坐标的差值
xMin、xMax、yMin、yMax、width、height:(xMin,yMin) -> (xMax,yMax)表示显示字符需要最小的坐标范围,其中width = xMax - xMinheight = yMax - yMin
brearingX、bearingY:brearingX = xMin - orgin_xbearingY = yMax - orgin_y

如何获得xMin、xMax、yMin、yMax?
 可以使用FT_Glyph_Get_CBox函数获得一个字体的这些参数,将会保存在一个FT_BBox结构体中,以后想计算一行文字的外框时要用到这些信息:

▲FT_BBox_结构体

有了上面的基础知识,那么现在如何在指定位置显示一行文字呢?
  首先如何确定这行文字的外框:每一个字符都有自己外框:xMin、xMax、yMin、yMax。把这些字符的xMin、yMin中的最小值取出来,把这些字符的xMax、yMax中的最大值取出来,就可以确定这行文字的外框了。
  然后就可以在指定位置(x,y)显示一行文字:

  ① 先指定第1个字符的原点pen坐标为(0, 0),计算出它的外框
  ② 再计算右边字符的原点,也计算出它的外框
  把所有字符都处理完后就可以用上面提到的方法得到一行文字的整体外框:假设外框左上角坐标为(x’, y’)。
  ③ 想在(x, y)处显示这行文字,调整一下pen坐标即可,因为我们这一系列操作只填写过一个坐标就是起始文字的原点pen坐标
  那么怎么调整呢?
  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是什么?中文位图就保存在glyph -> bitmap中,face -> glyph就是FT_GlyphSlot类型的变量

▲FT_Face包含成员变量示意图

FT_Glyph:字体文件中保存有字符的原始关键点信息,使用freetype的函数可以放大、缩小、旋转获得新的关键点,这些新的关键点保存在插槽中(注意:位图也是保存在插槽中)
  新的关键点使用FT_Glyph来表示,可以使用这样的代码从slot中获得glyph:error = FT_Get_Glyph(slot , &glyph);
FT_BBox:结构体定义如下,它表示一个字符的外框,即新glyph的外框:

▲FT_BBox_结构体

  可以使用FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &bbox );从glyph中取得FT_BBox信息

  下面的示例代码使用到了上面提到的数据结构和函数

▲示例代码

部分程序分析

显示字符串:先将pen设为(0,0)倒推原点(x’,y’),然后用(pen_x,pen_y) = (x,y )-(x',y')计算出pen
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)
{
    int i;
    int error;
    FT_BBox bbox;
    FT_Vector pen;
    FT_Glyph  glyph;
    FT_GlyphSlot slot = face->glyph;

    /* 把LCD坐标转换为笛卡尔坐标 */
    int x = lcd_x;
    int y = var.yres - lcd_y;

    /* 计算外框 */
    compute_string_bbox(face, wstr, &bbox);

    /* 反推原点 */
    //为什么 * 64?FT_Set_Transform函数要求输入坐标单位为1/64像素
    pen.x = (x - bbox.xMin) * 64; /* 单位: 1/64像素 */
    pen.y = (y - bbox.yMax) * 64; /* 单位: 1/64像素 */

    /* 处理每个字符 */
    for (i = 0; i < wcslen(wstr); i++)
    {
        /* 转换:transformation */
        //将pen内信息融合进face
        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 compute_string_bbox(FT_Face face, wchar_t *wstr, FT_BBox *abbox)函数

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;

    /* 初始化 */
    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;
        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, double angle)
{
    int i;
    int error;
    FT_BBox bbox;
    FT_Vector pen;
    FT_Glyph  glyph;
    FT_GlyphSlot slot = face->glyph;
    FT_Matrix	  matrix;				  /* transformation matrix */



    /* 把LCD坐标转换为笛卡尔坐标 */
    int x = lcd_x;
    int y = var.yres - lcd_y;

    /* 计算外框 */
    compute_string_bbox(face, wstr, &bbox);

    /* 反推原点 */
    //为什么 * 64?FT_Set_Transform函数要求输入坐标单位为1/64像素
    pen.x = (x - bbox.xMin) * 64; /* 单位: 1/64像素 */
    pen.y = (y - bbox.yMax) * 64; /* 单位: 1/64像素 */

    /* 处理每个字符 */
    for (i = 0; i < wcslen(wstr); i++)
    {
        
        /* 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 );

        /* 转换:transformation */
        //将pen内信息融合进face
        FT_Set_Transform( face, &matrix, &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;
}

猜你喜欢

转载自blog.csdn.net/u010164190/article/details/123556690
今日推荐