第三階段應用層——1.6 數碼相冊—使用FreeType在LCD上顯示多行文字

數碼相冊——使用FreeType在LCD上顯示多行文字

  • 硬件平臺:韋東山嵌入式Linxu開發板(S3C2440.v3)
  • 軟件平臺:運行於VMware Workstation 12 Player下UbuntuLTS16.04_x64 系統
  • 參考資料:《嵌入式Linux應用開發手冊》、《嵌入式Linux應用開發手冊第2版》、【FreeType官方教程—管理字形】
  • 開發環境:Linux 3.4.2內核、arm-linux-gcc 4.3.2工具鏈


一、FreeType的字形指標

1、爲什麼要引用字形指標

  1. 爲了準確的描述每個字形的信息呈現更好的顯示效果
  2. 爲了更好的兼容各國的文字呈現更好的顯示效果
    2.1 對於漢字,它是方框字,類似於小時候我們在田字格上漢字,給定一個矩形範圍,我們可以在這個範圍內寫出很工整的漢字
    2.2 對於英文,它的字形大小沒有固定的大小範圍,類似於小時候我們是在四線格中寫英文,對於其的高矮寬細無法用一個很好的矩形去總的描繪
    2.3 對於其他國家的文字也存在上述的問題
    在這裏插入圖片描述

2、什麼是字形指標

字形指標是與每個字形關聯的特定距離以描述如何使用它來佈局文本

對於單個字形,通常有兩組度量:用於在水平文本佈局(拉丁,西裏爾字母,阿拉伯語,希伯來語等)中設置字形的度量,以及用於在垂直文本佈局中對字形進行佈局的度量(中文,日語) ,韓文等)。

這裏針對水平佈局來進行介紹
在這裏插入圖片描述

  • origin自己定義
	FT_Vector     pen;
	pen.x = 10;
	pen.y = 10;
  • height、width獲取
    width = bbox.xMax - bbox.xMin;
    height = bbox.yMax - bbox.yMin;
  • advance獲取
	FT_Library    library;
	FT_Face       face;
	FT_GlyphSlot  slot;
	FT_Vector     pen;
	
	error = FT_Init_FreeType( &library );   
	error = FT_New_Face( library, argv[1], 0, &face ); 
	FT_Set_Pixel_Sizes(face, 24, 0);
	
	slot = face->glyph;
	pen.x += slot->advance.x;
	pen.y += slot->advance.y;
  • xMin、yMin、xMin、yMin存儲
	typedef struct  FT_BBox_
	{
		FT_Pos  xMin, yMin;
		FT_Pos  xMin, yMin;;	
	} FT_BBox;

二、在LCD中從左到右顯示多行文字

1、先確定該行origin座標並進行顯示,後計算出邊框

FreeType所使用的座標是笛卡爾座標,與LCD使用的座標不一樣
在這裏插入圖片描述
在這裏,我們需要顯示兩行文字,具體步驟如下:

  1. 確定第一行origin的LCD座標,後根據公式轉換爲笛卡爾座標
  2. 顯示該行每個字符的時候origin.x座標 + 上一個字符的advance;
  3. 在顯示第一行的時候,找尋該行最大的字符的height,即最小的yMin,最大的yMax
  4. 確定第二行的origin的LCD座標其x座標 == 第一行的origin.lcd_x其y座標 == 3步驟中得到的height+ origin.lcd_y == (yMax - yMin)+ origin.lcd_y)
    在這裏插入圖片描述

2、代碼

#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 <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H

/* 文件操作 */
static int fd_fb;
static unsigned char *fbmem;

/* lcd參數 */
static int screen_size;
static unsigned int line_width;
static unsigned int pixel_width;
static struct fb_var_screeninfo var;
static struct fb_fix_screeninfo fix;

/* lcd描色   color : 0x00RRGGBB */
void lcd_put_pixel(int x, int y, unsigned int color)
{	
	unsigned char  *pen_8;
	unsigned short *pen_16;
	unsigned int   *pen_32;
	unsigned int 	red, green, blue;

	/* 該座標在內存中對應像素的位置 */
	pen_8  = fbmem+y*line_width+x*pixel_width;
	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:
		/* RGB:565 */
		red   = ((color >> 16) & 0xffff) >> 3;
		green = ((color >> 8)  & 0xffff) >> 2;
		blue  = ((color >> 0)  & 0xffff) >> 3;
		*pen_16 = (red << 11) | (green << 5) | blue;
		break;
	case 32:
		*pen_32 = color;
		break;
	default:
		printf("can not surport &dbpp\n", var.bits_per_pixel);
		break;
	}
}

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

			//image[j][i] |= bitmap->buffer[q * bitmap->width + p];
			lcd_put_pixel(i, j, bitmap->buffer[q * bitmap->width + p]);
		}
	}
}

int main(int argc, char **argv)
{	
	wchar_t *wstr1 = L"百問網gif";
	wchar_t *wstr2 = L"www.100ask.com";

	FT_Library	  library;
	FT_Face 	  face;
    FT_Vector     pen;
	FT_GlyphSlot  slot;
	FT_Matrix	  matrix;
	FT_BBox 	  bbox;
	FT_Glyph  	  glyph;
	double        angle;
	int 		  error;
	int 		  line_box_ymin;
	int 		  line_box_ymax;
	int 		  i;

	line_box_ymin = 10000;
	line_box_ymax = 0;

	/* 提示信息 */
	if (argc != 3) {
		printf("Usage : %s <font_file> <angle>\n", argv[0]);
		return -1;
	}
	
	/* 打開設備:支持讀寫 */
	fd_fb = open("/dev/fb0", O_RDWR);
	if (fd_fb < 0) {
		printf("can not open /dev/fb0 , err code :%d\n", fd_fb);
		return -1;
	}
	
	/* 獲得可變信息 */
	if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var)) {
		printf("can not get var\n");
		return -1;
	}

	/* 獲得固定信息 */
	if (ioctl(fd_fb, FBIOGET_FSCREENINFO, &fix)) {
		printf("can not get var\n");
		return -1;
	}

	/* 直接映射到內存的Framebuffer */
	screen_size = var.xres * var.yres * var.bits_per_pixel / 8;	// 屏幕總像素所佔的字節數
	line_width   = var.xres * var.bits_per_pixel / 8;	// 每行像素所佔的字節數
	pixel_width  = 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 not mmap\n");
		return -1;
	}

	/* 清屏 */
	memset(fbmem, 0, screen_size);
	
	/* 顯示矢量字 */
	/* 初始化庫 */
	error = FT_Init_FreeType(&library);

	/* 裝載字體文件 */
	error = FT_New_Face(library, argv[1], 0, &face);
	slot = face->glyph;

	/* 設置大小:24*24 */
	FT_Set_Pixel_Sizes(face, 24, 0);

	/* 設置旋轉角度 */
	angle = (1.0 * strtoul(argv[2], NULL, 0) / 360) * 3.14159 * 2;	  /* use 25 degrees 	*/

	/* 設置矩陣 */
	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);

	/* 確定座標:
	 * lcd_x = 0
	 * lcd_y = 24
	 * 笛卡爾座標系:
	 * x = lcd_x = 0
	 * y = var.yres - lcd_y = var.yres - 24
	 */
	pen.x = 0 * 64;
	pen.y = (var.yres - 24) * 64;

	/* 顯示第一行文字 */
	for (i = 0; i < wcslen(wstr1); i++) {
		/* 設置轉換方法 */
		FT_Set_Transform(face, &matrix, &pen);

		/* 根據給定的文字信息,加載文字 */
		error = FT_Load_Char(face, wstr1[i], FT_LOAD_RENDER);
		if (error) {
			printf("FT_Load_Char error\n");
			return -1;
		}

		/* 從插槽中取出face->glyph,存到glyph中 */
		error = FT_Get_Glyph(face->glyph, &glyph);	
		if (error) {
			printf("FT_Get_Glyph error!\n");
			return -1;
		}

		/* 得到相關字體參數信息 */
		FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &bbox);

		/* 尋該行最大的字符的height,即最小的yMin,最大的yMax */
		if (line_box_ymax < bbox.yMax) line_box_ymax = bbox.yMax;
		if (line_box_ymin > bbox.yMin) line_box_ymin = bbox.yMin;

		/* 描繪位圖信息 */
		draw_bitmap(&slot->bitmap, slot->bitmap_left, var.yres - slot->bitmap_top);

		/* 打印位圖信息 */
		printf("Unicode: 0x%x\n", wstr1[i]);
		printf("origin.x/64 = %d, origin.y/64 = %d\n", pen.x/64, pen.y/64);
		printf("xMin = %d, xMax = %d, yMin = %d, yMax = %d\n", bbox.xMin, bbox.xMax, bbox.yMin, bbox.yMax);
		printf("slot->advance.x/64 = %d, slot->advance.y/64 = %d\n", slot->advance.x/64, slot->advance.y/64);

		/* 移動筆的位置:同一水平方向 */
		pen.x += slot->advance.x;
	}
	
	/* 確定座標:
	 * lcd_x = 0
	 * lcd_y = line_box_ymax - line_box_ymin + 24
	 * 笛卡爾座標系:
	 * x = lcd_x = 0
	 * y = var.yres - lcd_y = var.yres - (line_box_ymax - line_box_ymin + 24)
	 */
	pen.x = 0 * 64;
	pen.y = (var.yres - (line_box_ymax - line_box_ymin + 24)) * 64;
	
	/* 顯示第二行文字 */
	for (i = 0; i < wcslen(wstr2); i++) {		
		/* 設置轉換方法 */
		FT_Set_Transform(face, &matrix, &pen);

		/* 根據給定的文字信息,加載文字 */
		error = FT_Load_Char(face, wstr2[i], FT_LOAD_RENDER);
		if (error) {
			printf("FT_Load_Char error\n");
			return -1;
		}

		/* 從插槽中取出face->glyph,存到glyph中 */
		error = FT_Get_Glyph(face->glyph, &glyph);	
		if (error) {
			printf("FT_Get_Glyph error!\n");
			return -1;
		}

		/* 得到相關字體參數信息 */
		FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &bbox);

		/* 尋該行最大的字符的height,即最小的yMin,最大的yMax */
		if (line_box_ymax < bbox.yMax) line_box_ymax = bbox.yMax;
		if (line_box_ymin > bbox.yMin) line_box_ymin = bbox.yMin;

		/* 描繪位圖信息 */
		draw_bitmap(&slot->bitmap, slot->bitmap_left, var.yres - slot->bitmap_top);

		/* 打印位圖信息 */
		printf("Unicode: 0x%x\n", wstr1[i]);
		printf("origin.x/64 = %d, origin.y/64 = %d\n", pen.x/64, pen.y/64);
		printf("xMin = %d, xMax = %d, yMin = %d, yMax = %d\n", bbox.xMin, bbox.xMax, bbox.yMin, bbox.yMax);
		printf("slot->advance.x/64 = %d, slot->advance.y/64 = %d\n", slot->advance.x/64, slot->advance.y/64);

		/* 移動筆的位置:同一水平方向 */
		pen.x += slot->advance.x;
	}
	return 0;
}

3、編譯與運行

  • 執行arm-linux-gcc -finput-charset=GBK -o show_line_left show_line_left.c -lfreetype -lm
    在這裏插入圖片描述
  • show_line_left可執行文件傳到開發板,執行./show_line_left ./simsun.ttc運行
    在這裏插入圖片描述

三、在LCD中居中顯示多行文字

1、先計算出該行字體的邊框,確定其origin座標後描繪

在這裏,我們需要顯示兩行文字,具體步驟如下:

  1. 執行函數Get_Glyphs_Form_Wstr()根據字符串得到每個字符的glyph,並把glyph存儲到Glyphs數組中
  2. 執行函數compute_string_bbox():根據上述得到的Glyphs數組,計算得到該字符串的邊框
  3. 確定第一行字符串的origin座標注意此時是笛卡爾座標
    在這裏插入圖片描述
  4. 進行描繪
  5. 描繪第二行的時候重複上述步驟

2、代碼

根據官方文檔資料 【5.高級文本渲染:轉換,居中和緊縮】進行編寫

#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 <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H

/* FreeType */
#define MAX_GLYPHS 100

typedef struct  TGlyph_
{
	FT_UInt    index;  /* glyph index                  */
	FT_Vector  pos;    /* glyph origin on the baseline */
	FT_Glyph   image;  /* glyph image                  */
} TGlyph, *PGlyph;

/* 文件操作 */
static int fd_fb;
static unsigned char *fbmem;

/* lcd參數 */
static int screen_size;
static unsigned int line_width;
static unsigned int pixel_width;
static struct fb_var_screeninfo var;
static struct fb_fix_screeninfo fix;

/* lcd描色   color : 0x00RRGGBB */
void lcd_put_pixel(int x, int y, unsigned int color)
{	
	unsigned char  *pen_8;
	unsigned short *pen_16;
	unsigned int   *pen_32;
	unsigned int 	red, green, blue;

	/* 該座標在內存中對應像素的位置 */
	pen_8  = fbmem+y*line_width+x*pixel_width;
	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:
		/* RGB:565 */
		red   = ((color >> 16) & 0xffff) >> 3;
		green = ((color >> 8)  & 0xffff) >> 2;
		blue  = ((color >> 0)  & 0xffff) >> 3;
		*pen_16 = (red << 11) | (green << 5) | blue;
		break;
	case 32:
		*pen_32 = color;
		break;
	default:
		printf("can not surport &dbpp\n", var.bits_per_pixel);
		break;
	}
}

 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;

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

/* 根據字符串得到Glyph,並把Glyph存儲到Glyphs數組中 */
int Get_Glyphs_Form_Wstr(FT_Face           face, wchar_t *wstr, TGlyph glyphs[])
{
	int n;
	int error;
	int pen_x;
	int pen_y;
	PGlyph glyph;
	FT_GlyphSlot slot;

	glyph = glyphs;
	slot  = face->glyph;
	pen_x = 0;
	pen_y = 0;
	
	for (n = 0; n < wcslen(wstr); n++) {
		/* 根據該字符的Unicode碼,獲得在face中該字符的索引 */
		glyph->index = FT_Get_Char_Index(face, wstr[n]);

		/* 記錄描繪座標 */
		glyph->pos.x = pen_x;
		glyph->pos.y = pen_y;

		/* 從face中根據glyph->index把glyph加載到插槽face->glyph中,不需要轉換爲位圖 */
		error = FT_Load_Glyph(face, glyph->index, FT_LOAD_DEFAULT);
    	if (error) continue;

		/* 從插槽face->glyph中把信息加載到glyph->image中 */
		error = FT_Get_Glyph(face->glyph, &glyph->image);
		if (error) continue;

		/* 設置變換方式:使得glyph->image中含有位置信息 */
		FT_Glyph_Transform(glyph->image, 0, &glyph->pos);

		/* 下一個字符的原點x座標 */
		pen_x += slot->advance.x;

		glyph++;
	}

	/* 返回glyph的個數 */
	return (glyph - glyphs);
}

/* 計算Glyphs數組中的字符串的邊框 */
void compute_string_bbox(TGlyph glyphs[], FT_UInt num_glyphs, FT_BBox *abbox)
{
	FT_BBox bbox;
	int n;

	bbox.xMin = bbox.yMin =  32000;
    bbox.xMax = bbox.yMax = -32000;

	for (n = 0; n < num_glyphs; n++) {
		FT_BBox  glyph_bbox;

		/* 以像素爲單位從glyphs[n]的數組項取值,得到該字符的邊框box */
		FT_Glyph_Get_CBox(glyphs[n].image, FT_GLYPH_BBOX_TRUNCATE, &glyph_bbox);

		/* 尋該行最大的字符的height和width,即最小的yMin和xMin,最大的yMax和xMax */
		if (bbox.xMin > glyph_bbox.xMin) bbox.xMin = glyph_bbox.xMin;
		if (bbox.xMax < glyph_bbox.xMax) bbox.xMax = glyph_bbox.xMax;
		
		if (bbox.yMin > glyph_bbox.yMin) bbox.yMin = glyph_bbox.yMin;
		if (bbox.yMax < glyph_bbox.yMax) bbox.yMax = glyph_bbox.yMax;
	}
	
	*abbox = bbox;
}

/* 描繪Glyphs中的信息——渲染 */
void Draw_Glyphs(TGlyph glyphs[], FT_UInt num_glyphs, FT_Vector pen)
{
	int n;
	int error;

	for (n = 0; n < num_glyphs; n++) {
		/* 設置變換方式:使得glyph->image中含有位置信息 */
		FT_Glyph_Transform(glyphs[n].image, 0, &pen);

		/* 把gylph轉換爲位圖 */
		error = FT_Glyph_To_Bitmap(&glyphs[n].image, FT_RENDER_MODE_NORMAL,   0, 1);                /* destroy copy in "image"   */
		if (!error) {
			/* 獲得位圖 */
			FT_BitmapGlyph bit = (FT_BitmapGlyph)glyphs[n].image;

			/* 描繪位圖 */
			draw_bitmap(&bit->bitmap, bit->left, var.yres - bit->top);

			/* 釋放空間 */
			FT_Done_Glyph(glyphs[n].image);
		}
	}
}

int main(int argc, char **argv)
{	
	wchar_t *wstr1 = L"百問網gif";
	wchar_t *wstr2 = L"www.100ask.com";

	FT_Library	  library;
	FT_Face 	  face;
    FT_Vector     pen;
	FT_GlyphSlot  slot;
	FT_Matrix	  matrix;
	FT_BBox 	  bbox;
	FT_UInt       num_glyphs;
	TGlyph        glyphs[MAX_GLYPHS];	//負責存儲字符信息的數組
	PGlyph        glyph;
	double        angle;
	int 		  error;
	int 		  line_box_ymin;
	int 		  line_box_ymax;
	int 		  line_box_height;
	int 		  line_box_width;
	int 		  i;
	int num;

	line_box_ymin = 10000;
	line_box_ymax = 0;
	
	/* 打開設備:支持讀寫 */
	fd_fb = open("/dev/fb0", O_RDWR);
	if (fd_fb < 0) {
		printf("can not open /dev/fb0 , err code :%d\n", fd_fb);
		return -1;
	}
	
	/* 獲得可變信息 */
	if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var)) {
		printf("can not get var\n");
		return -1;
	}

	/* 獲得固定信息 */
	if (ioctl(fd_fb, FBIOGET_FSCREENINFO, &fix)) {
		printf("can not get var\n");
		return -1;
	}

	/* 直接映射到內存的Framebuffer */
	screen_size = var.xres * var.yres * var.bits_per_pixel / 8;	// 屏幕總像素所佔的字節數
	line_width   = var.xres * var.bits_per_pixel / 8;	// 每行像素所佔的字節數
	pixel_width  = 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 not mmap\n");
		return -1;
	}

	/* 清屏 */
	memset(fbmem, 0, screen_size);
	
	/* 顯示矢量字 */
	/* 初始化庫 */
	error = FT_Init_FreeType(&library);

	/* 裝載字體文件 */
	error = FT_New_Face(library, argv[1], 0, &face);
	slot = face->glyph;

	/* 設置像素大小:24*24 */
	FT_Set_Pixel_Sizes(face, 24, 0);

	/* 顯示第一個字符串 */
	/* 根據字符串得到字符的glyph,並把glyph存儲到Glyphs數組中 */
	num_glyphs = Get_Glyphs_Form_Wstr(face, wstr1, glyphs);	

	/* 計算Glyphs數組中的字符串的邊框 */
	compute_string_bbox(glyphs, num_glyphs, &bbox);

	/* 計算一行中最高的高度和最寬的寬度 */
	line_box_height = bbox.yMax - bbox.yMin;
	line_box_width  = bbox.xMax - bbox.xMin;

	/* 確定原點座標 */
	pen.x = (var.xres - line_box_width)/2  * 64;
	pen.y = (var.yres - line_box_height)/2 * 64;

	/* 描繪Glyphs中的信息 */
	Draw_Glyphs(glyphs, num_glyphs, pen);

	/* 顯示第二個字符串 */
	/* 根據字符串得到Glyph,並把Glyph存儲到Glyphs數組中 */
	num_glyphs = Get_Glyphs_Form_Wstr(face, wstr2, glyphs);	

	/* 計算Glyphs數組中的字符串的邊框 */
	compute_string_bbox(glyphs, num_glyphs, &bbox);

	/* 計算一行中最高的高度和最寬的寬度 */
	line_box_height = bbox.yMax - bbox.yMin;
	line_box_width  = bbox.xMax - bbox.xMin;

	/* 確定原點座標 */
	pen.x = (var.xres - line_box_width)/2  * 64;
	pen.y = pen.y - 24 * 64;

	/* 描繪Glyphs中的信息 */
	Draw_Glyphs(glyphs, num_glyphs, pen);
	
	return 0;
}

3、編譯與運行

  • 執行arm-linux-gcc -finput-charset=GBK -o show_line_center show_line_center.c -lfreetype -lm
    在這裏插入圖片描述
  • show_line_center可執行文件傳到開發板,執行./show_line_center ./simsun.ttc運行
    在這裏插入圖片描述
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章