[电子书]项目储备二:用FreeType2绘制矢量字体、多行文字

  • 0 矢量字体 VS 点阵字体
  • 1 FreeType2的介绍与基本使用
  • 2 在LCD显示一个矢量字体,且可旋转
  • 3 在LCD任意位置显示多行文字

0 矢量字体 VS 点阵字体

点阵字体:每一个字符都用点阵(8x8 / 8x16/ 16x16等)表示,然后用每个点的虚实来表示字符的轮廓
矢量字体:矢量字体(Vector font)中每一个字形是通过数学曲线来描述的,它包含了字形边界上的关键点,连线的导数信息等,
字体的渲染引擎(如FreeType2)通过读取这些数学矢量,然后进行一定的数学运算来进行渲染。
矢量字体主要包括 Type1、 TrueType、OpenType等几类。两者的区别:
1)拓展名:矢量字体扩展名ttf。点阵字体的扩展名是fon.(在window10下,C盘windows/fonts/目录下有许多字体文件)
2)点阵字体组成的点阵字库 常用来保存显示字符点阵,这类点阵字库汉字最大的缺点是不能放大,一旦放大后就会发现文字边缘的锯齿。而 矢量字体组成的矢量字库只保存该字符关键点和其它一些描述信息(比如一个笔划的起始、终止坐标,半径、弧度等)。在显示、打印这一类字库时,要经过一系列的数学运算才能输出结果,但是这一类字库保存的汉字理论上可以被无限地放大,笔划轮廓仍然能保持圆滑,打印时使用的字库均为此类字库两者的优劣势:
①点阵字体: 点阵字体优点是显示速度快,不像矢量字体需要计算;其最大的缺点是不能放大,一旦放大后就会发现文字边缘的锯齿。
②矢量字体:优点很明显显示效果比点阵字体好,且随意变换(放大、缩小、旋转等)不失真。缺点需要渲染方能显示、打印,因此相比点阵字体肯定要慢。

矢量字体是后文要实现绘制,那如何使用矢量字体呢?
需要从矢量字库从提取字符的关键点,然后用若干条数学曲线(贝塞尔曲线)连接绘制(由FreeType2渲染完成)。
因此,实际编程时,只需要使用FreType2提供的高层API即可,此外,难点在于需要我们
把转换后的位图绘制到LCD屏幕上。

1 FreeType2的介绍与基本使用

FreeType库是一个完全免费(开源)的、高质量的且可移植的字体引擎,它提供统一的接口来访问多种字体格式文件,
包括TrueType, OpenType, Type1, CID, CFF, Windows FON/FNT, X11 PCF等FreeType2的基本使用如下图


后面绘制矢量字体、多行文字也是按此流程编程实现
用文字来描述FreeType的基本使用,如下所述
1)给定一个文字,可得到确定它的字符编码值
2)根据字符“编码值”从矢量字体文件中找到“glyph(字形/字的轮廓,其实就是字形的一些关键点)”
3)设置字体大小(因为是矢量字体,大小随意,固然需确定显示字符大小啦)
4)用某些函数吧Glyph里的关键点编码为指定字体大小
5)转换为位图点阵
6)在LCD上显示出来

废话不多说,先看看如果用FreeType2渲染一个矢量字体,并能随意旋转

2 在LCD显示一个矢量字体,且可旋转

方便起见,在LCD中央显示一个“繁”字,并根据命令行参数控制旋转角度
show_vector_font.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <math.h>
#include <wchar.h>
#include <ft2build.h>
#include FT_FREETYPE_H  // freetype/freetype.h
#include FT_GLYPH_Hextern const unsigned char fontdata_8x16[];
int fd_fb;
static line_width, pixel_width, screen_size;
static unsigned char* fbmem;
static struct fb_var_screeninfo var;
static struct fb_fix_screeninfo fix;int error;
double angle;FT_Library  library;
FT_Face     face;      /* handle to face object */
FT_GlyphSlot slot;
FT_Vector     pen;                 /* untransformed origin */
FT_Matrix   matrix;void lcd_put_pixel(unsigned int x, unsigned y, unsigned int color);
void my_draw_bitmap( FT_Bitmap*  bitmap, FT_Int x, FT_Int  y);int main(int argc, char** argv)
{wchar_t *str1 = L"繁";if(argc != 3){printf("Usage: ");printf("%s <font_file> <angle>\n", argv[0]);return 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 * pixel_width;fbmem=(unsigned char*)mmap(NULL, screen_size,PROT_READ | PROT_WRITE, MAP_SHARED,fd_fb, 0);if(fbmem == (unsigned char*)-1){printf("can't map fbmem\n");b         return -1;}memset(fbmem, 0, screen_size); // clear screen // show a vector font error = FT_Init_FreeType(&library);error = FT_New_Face( library, argv[1], 0, &face );slot = face->glyph;error = FT_Set_Pixel_Sizes(face,   /* handle to face object */24,      /* pixel_width           */0);   /* pixel_height          */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 );pen.x = (var.xres/2 ) * 64;pen.y = (var.yres/2 ) * 64;FT_Set_Transform(face,       /* target face object    */&matrix,    /* pointer to 2x2 matrix */&pen);   /* pointer to 2d vector  */'aT_Load_Char( face, str1[0], FT_LOAD_RENDER );my_draw_bitmap( &slot->bitmap,slot->bitmap_left,var.yres - slot->bitmap_top );// 记得释放                      munmap(fbmem, screen_size);FT_Done_Face(face);FT_Done_FreeType(library);return 0;
}void lcd_put_pixel(unsigned int x, unsigned y, unsigned int color)
{unsigned char* pen_8 = fbmem + y*line_width + x*pixel_width;unsigned ort* pen_16 = NULL;unsigned int* pen_32 = NULL;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:{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'z:{*pen_32 = color ;break;}default:{printf("can't surport %dbpp\n",var.bits_per_pixel);return ;}}
}
void my_draw_bitmap( FT_Bitmap*  bitmap, FT_Int x, FT_Int  y)
{printf("x:%d, y:%d\n", x, 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   ||ao                 i >= var.xres || j >= var.yres )continue;lcd_put_pixel(i, j, bitmap->buffer[q * bitmap->width + p]);}}
}

好了,如何交叉编译并测试呢?

  1. tar xjf freetype-2.4.10.tar bz2
  2. .configure --host=arm-linux --prefix=xxxx
    (xxx是存放交叉编译工具链头文件和库的目录,一般来说是目录是相同的,我的就是。
    如果不是,则不需–prefix=xxx,在后面make install DESTDIR=$PWD/tmp 安装到临时目录
    然后再把freetype的库文件和库复制到交叉编译工具链对应的头文件和库目录下
    )
  3. make
  4. make DESTDIR=$PWD/tmp install (如果目录相同,DESTDIR不需要)

最后编译:
arm-linux-gcc -finput-charset=GBK -fexec-charset=GBK -o show_vector_font show_vector_font.c
-lfreetype -lm

将生成的show_vector_font测试文件和字体文件(可从C盘windows/fonts/目录下随便获取一个)复制
到开发板的根文件系统
另外,记得也要将freetype的动态库文件复制到根文件系统的/lib目录下

然后测试
./show_vector_font simsun.ttc 45

最后,看下面的测试结果图.

ok,这个简单例子只是掌握FreeType2的基本用法
下面来个更高级一些的用法,在LCD任意位置显示多行文字
测试效果:以逆时针旋转45°为例,其它角度也OK。要顺时针的话,要调整那个变换矩阵(我数学渣渣,留给你们:)

3 在LCD任意位置显示多行文字

有了前面显示单个矢量字体,对于显示多行文字就简单了,直接贴出代码,如下
备注:全部贴出太占篇幅了,也没必要。因此贴出关键代码,其它和显示单个矢量字体一样完整源码可从我的代码集合那篇博文获取
int main(int argc, char** argv)
{   wchar_t *str = L"中国我爱你\n我爱你中国";/* ... */draw_lines(200, 100, str);
}
int draw_lines(unsigned int x, unsigned int y, wchar_t* str)
{if(x > var.xres || y > var.yres || !str)return -1;printf("xres:%d yres:%d\n",var.xres, var.yres);pen.x = x * 64;pen.y = (var.yres - y) * 64;int n;for(n = 0; n < wcslen(str)i; ++n){FT_Set_Transform(face,       /* target face object    */0,&pen);   /* pointer to 2d vector  */FT_Load_Char( face, str[n], FT_LOAD_RENDER );my_draw_bitmap( &slot->bitmap,slot->bitmap_left,var.yres - slot->bitmap_top );pen.x += slot->advance.x;if(str[n] == '\n'){pen.y = (var.yres - y - 24)*64;pen.x = x * 64;b }}
}

测试效果:

细心的朋友会发现,其实换行的时候,我给定了下一行文字的起点(即设置的pen.x pen.y)
打印汉字没问题,但是如果中英文参杂时,比如g,会不会出现混叠呢?
其实会的,这里有个解决办法,也是要讲解的重点——提取计算下一行(整行, 而不是单个字体)的最大宽和高
修改后的代码如下:

typedef struct TGlyph { FT_UInt index; /* glyph index */ FT_Vector pos; /* glyph origin on the baseline */ FT_Glyph image; /* glyph image */
} TGlyph, *PGlyph; /* 从一行字符串中获取glyph保存到glyphs[]数组的image */
int Get_Glyphs_Frm_Wstr(FT_Face face, wchar_t *wstr, TGlyph glyphs[])
{int n;PGlyph glyph = glyphs;int pen_x = 0;int pen_y = 0;int error;FT_GlyphSlot slot = face->glyph;for(n = 0; n < wcslen(wstr); n++){glyph->index = FT_Get_Char_Index(face, wstr[n]);glyph->pos.x = pen_x;glyph->pos.y = pen_y;if(wstr[n] == '\n')  // 计算的字符串以换行符作为一行结束的标志break;error = FT_Load_Glyph(face, glyph->index, FT_LOAD_DEFAULT);if(error)continue;error = FT_Get_Glyph(face->glyph, &glyph->image);if(error)continue;FT_Glyph_Transform(glyph->image, 0, &glyph->pos);pen_x += slot->advance.x;glyph++;}return (glyph - glyphs);
}
/* 根据glyphs[]数组保存的glyph计算每个glyph的cbox,cbox里存有xMin xMax yMin yMax */
/* 然后依次比较每个glyph的cbox以获得xMin xMax yMin yMax,并把最终结果保存到abbox */
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 glyphs_bbox;FT_Glyph_Get_CBox(glyphs[n].image, FT_GLYPH_BBOX_TRUNCATE, &glyphs_bbox);if(glyphs_bbox.xMin < bbox.xMin)bbox.xMin = glyphs_bbox.xMin;if(glyphs_bbox.yMin < bbox.yMin)bbox.yMin = glyphs_bbox.yMin;if(glyphs_bbox.xMax > bbox.xMax)bbox.xMax = glyphs_bbox.xMax;if(glyphs_bbox.yMax > bbox.yMax)bbox.yMax = glyphs_bbox.yMax;}*abbox = bbox;
}
int draw_lines(unsigned int x, unsigned int y, wchar_t* str)
{FT_UInt num_glyphs;TGlyph glyphs[MAX_GLYPHS]; /* glyphs table */FT_BBox bbox;int line_box_width;int line_box_height;int count;if(x > var.xres || y > var.yres || !str)return -1;printf("xres:%d yres:%d\n",var.xres, var.yres);pen.x = x * 64;pen.y = (var.yres - y) * 64;int n;for(n = 0; n < wcslen(str); ++n){if(str[n] == '\n'){n += 1;// 注意要获取上行的宽、高num_glyphs = Get_Glyphs_Frm_Wstr(face, &str[count], glyphs);compute_string_bbox(glyphs, num_glyphs, &bbox);line_box_height = bbox.yMax - bbox.yMin;line_box_width  = bbox.xMax - bbox.xMin;pen.y = pen.y -(line_box_height + 1) * 64; // 注意 -1 pen.x = x * 64;count = num_glyphs;}FT_Set_Transform(face,       /* target face object    */0,&pen);   /* pointer to 2d vector  */FT_Load_Char( face, str[n], FT_LOAD_RENDER );my_draw_bitmap( &slot->bitmap,slot->bitmap_left,var.yres - slot->bitmap_top );pen.x += slot->advance.x;}
}

注意在计算得到行高后,记得-1,不然会出现覆盖现象,如下图所示

-1之后,最终效果:

至于为什么-1呢? 因为,比如说 23 至 8 差多少, 你不能说是 23-8=15吧,而应该是 23 -8 + 1 = 16才对,包括 8 本身。
因此,这里需要减一操作,不然覆盖了呀

再来看看,如果显示四行内容,能成功吗?看下图

好了,这篇文章告一段落,原本打算绘制图片的,考虑到篇幅问题,留给下篇博文吧 :)

如果有任何疑问、错误,欢迎指出来噢 :)
一起进步呀,加油!

[电子书]项目储备二:用FreeType2绘制矢量字体、多行文字相关推荐

  1. [电子书]项目储备一: 字符编码与LCD显示单个字母/汉字

    [电子书]项目储备一: 字符编码与LCD显示单个字母/汉字 1 字符编码 2 LCD显示单个字母/汉字 2.1 编程思路 2.2 代码实现 2.3 编译.测试与bug 3 总结与下文预告 1 字符编码 ...

  2. WindML、FreeType和TrueType三者相结合实现矢量字体的显示

    1 VxWorks5.5点阵字库的局限性 VxWorks5.5 是美国风河公司开发的嵌入式操作系统,图形系统采用WindML3.0,支持点阵字显示,不支持矢量字体显示.点阵字采用内存模式加载,使用前需 ...

  3. 【项目1_电子书】第2.3.3课、在LCD上显示一个矢量字体

    主 机:VMWare--Ubuntu-16.04.2-x64-100ask 开发板:Mini2440--256M NandFlash, 2M NorFlash, 64M SDRAM, LCD-TD35 ...

  4. Linux电子书项目之freetype实现矢量字体的显示(2)

    上一篇博文我们通过点阵数组实现了字母的显示:http://blog.csdn.net/shenhuan1104/article/details/79476053 现在我们通过freetype这个框架在 ...

  5. Taro多端开发实现原理与项目实战(二)

    Taro多端开发实现原理与项目实战(二) 多端电商平台项目概述及开发准备 学习了前面的基础知识和进阶后是否跃跃欲试?我们准备了一个电商平台的项目来和大家一起实践使用 Taro 开发电商平台. 项目概述 ...

  6. Android自定义视图二:如何绘制内容

    这个系列是老外写的,干货!翻译出来一起学习.如有不妥,不吝赐教! Android自定义视图一:扩展现有的视图,添加新的XML属性 Android自定义视图二:如何绘制内容 Android自定义视图三: ...

  7. android绘制矢量图_Android矢量可绘制

    android绘制矢量图 In this tutorial, we'll be discussing Android Vector Drawable. Furthermore, we'll be im ...

  8. 思维导图(二):绘制规则

    思维导图有其自身的规则和技巧,对于初学者来说,掌握这些规则和技巧是非常必要的.只有在理解并熟练掌握这些技巧之后,绘图者才可以根据自己的意愿去发展属于自己的思维导图技巧和规则. 关键词使用规则 规则 1 ...

  9. 对P5基本二维图像绘制库的交互性扩展

    5379@TOC 对P5基本二维图像绘制库的交互性扩展 因为课程需要接触学习了P5.js ,觉得还蛮有意思的 下面是对它的2D绘图函数的一些简单扩展. ///终于等到你/ 无非是用周期性.随机性.对称 ...

最新文章

  1. swift使用xib绘制UIView
  2. 【关于封装的那些事】 缺失封装 【关于封装的那些事】 泄露的封装 【关于封装的那些事】 不充分的封装 【图解数据结构】二叉查找树 【图解数据结构】 二叉树遍历...
  3. 说一说为什么gethostbyname用完后不用释放内存
  4. 操作系统(十一)处理机调度概述
  5. assertequal用法python_assertEqual和assertIs之间有什么区别(assertIs是在Python 2.7中引入的)?...
  6. retrofit content-length为0_大佬们,一波RxJava 3.0来袭,请做好准备~
  7. 【渝粤教育】国家开放大学2018年秋季 0695-21T (1)农业企业经营管理 参考试题
  8. jquery获取select中的option的text值
  9. Python 集合、序列基础知识
  10. Python编程及应用--数据分析与科学计算可视化培训班
  11. SpringCloud工作笔记0104---SpringCloud和SpringCloudAlibaba的区别
  12. 东北方言编程,程序员咋这么招人稀罕!
  13. 字节跳动又启动期权回购了,这次价格142美元,较上一轮回购价涨8%
  14. ps基础学习:渐变工具制作倒影效果
  15. 微博开放平台-发微博测试
  16. windows10官网下载安装(纯净版)
  17. 已10万人集齐,支付宝集五福今日正式开始
  18. 工业机器人介绍及机器人学概述
  19. LeetCode 1348. 推文计数
  20. UVa 1600 巡逻机器人(Patrol Robot)

热门文章

  1. python reload is not defined_name 'reload' is not defined解决方法
  2. 【精品专栏】热销排行榜·0315-0321
  3. 虚伪的友情再见!!!!
  4. 在哪里可查看下载CELL《细胞》文献
  5. 软件测试基础知识——全
  6. 数据中心EOR/MOR/TOR布线概念(整理)
  7. 【java】java定时任务1秒调度一次会怎么样
  8. 现代计算机的二进制算法,源自中国智慧神秘巨著《周易》
  9. Java11新特性一览
  10. 【图像分类】【深度学习】ViT算法Pytorch代码讲解