[FreeType]提取、绘制字体的字形轮廓

C/C++第三方开源库的介绍和相关讨论
回复
头像
523066680
Administrator
Administrator
帖子: 573
注册时间: 2016年07月19日 12:14
联系:

[FreeType]提取、绘制字体的字形轮廓

帖子 523066680 »

1楼:最小示例代码、坐标解读
2楼:坐标解读、完善输出
2楼:带错误判断的示例代码
3楼:
#include <stdio.h>
#include <string.h>
#include <ft2build.h>
#include <freetype/ftoutln.h>
#include FT_FREETYPE_H

FT_Library library;
FT_Face face;
FT_GlyphSlot slot;
FT_Error error;
FT_Outline outline;

void ftinit(void)
{
char* filename;
filename = "C:/windows/fonts/consola.ttf";
FT_Init_FreeType( &library ); //初始化库实例(句柄)
FT_New_Face( library, filename, 0, &face ); //初始化face对象,设置字体
}

void LoadGlyph(FT_ULong symbol)
{
//获取符号对应的索引,可以是unicode编码值(如果该字体支持)
FT_UInt index = FT_Get_Char_Index(face, symbol);

//装载字符
FT_Load_Glyph(face,
index,
FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP);

//装载字符到face后,可以从字形槽获取点阵信息,也可以获取轮廓信息
slot = face->glyph; //字形槽
outline = slot->outline; //轮廓对象
}

int
main( int argc, char** argv )
{

ftinit();
LoadGlyph( 'a' );

printf("n_contours: %d \n n_points: %d \n flags: %d\n",
outline.n_contours, //轮廓曲线的数量
outline.n_points, //坐标点数量
outline.flags //暂时不确定含义
);

for (int i=0; i<outline.n_contours; i++ )
{
printf("Contour %d: %d\n", i, outline.contours[i]);
}
printf("\n");

for (int i = 0; i<outline.n_points ; i++)
{
printf("%2d : %4d %4d %d\n",
i,
outline.points[i].x,
outline.points[i].y,
outline.tags[i]
);
}

FT_Done_Face ( face );
FT_Done_FreeType( library );

return 0;
}
单纯的提取,没有错误判断
编译参考(其中-lpng -lz不是必须的)
gcc -std=c11 Simple_Outline.c -o Simple_Outline ^ -ID:/lib/freetype-2.7/include ^ -LD:/lib/freetype-2.7/objs/.libs ^ -lfreetype -lpng -lz
输出结果
n_contours: 2
n_points: 48
flags: 0
Contour 0: 32
Contour 1: 47

0 : 805 0 1
1 : 801 135 1
2 : 719 54 0
3 : 550 -18 0
4 : 457 -18 1
5 : 371 -18 0
6 : 249 26 0
7 : 170 103 0
8 : 133 207 0
9 : 133 268 1
10 : 133 419 0
11 : 358 590 0
12 : 578 590 1
13 : 786 590 1
14 : 786 678 1
15 : 786 767 0
16 : 672 874 0
17 : 555 874 1
18 : 470 874 0
19 : 305 836 0
20 : 217 801 1
21 : 217 958 1
22 : 250 970 0
23 : 331 993 0
24 : 421 1011 0
25 : 519 1022 0
26 : 569 1022 1
27 : 660 1022 0
28 : 806 982 0
29 : 907 900 0
30 : 961 776 0
31 : 961 692 1
32 : 961 0 1
33 : 786 457 1
34 : 565 457 1
35 : 500 457 0
36 : 406 431 0
37 : 346 383 0
38 : 317 316 0
39 : 317 274 1
40 : 317 245 0
41 : 335 192 0
42 : 375 151 0
43 : 439 127 0
44 : 485 127 1
45 : 545 127 0
46 : 700 200 0
47 : 786 279 1
这些坐标画出来是这样子的:
simple2.png
(11.06 KiB) 已下载 7116 次
左边是点绘,右边是线绘(没有划分),绿色是初始点,橙色是末尾点。正确解读请移步二楼
头像
523066680
Administrator
Administrator
帖子: 573
注册时间: 2016年07月19日 12:14
联系:

FreeType Outlines 原始数据结构、轮廓坐标解读、绘制

帖子 523066680 »

FreeType Outlines 数据结构、轮廓坐标解读(暂时解说 TrueType 字体)

参考官方文档
FT_Outline
顺便做翻译,不清楚的暂时保留英文
typedef struct  FT_Outline_
{
short n_contours; /* 轮廓的数量 */
short n_points; /* 坐标数量 */

FT_Vector* points; /* 轮廓坐标(数组) */
char* tags; /* 每组坐标的标签 */
short* contours; /* 轮廓边界位置(数组) */

int flags; /* outline masks */

} FT_Outline;
解释 n_contours 轮廓的数量,比如字符a和o,就分为内外两个轮廓 points 坐标,数组,长度为 n_points。FT_Vector是顶点类型,提取坐标数据的时候像这样: .points[n].x .points[n].y tags 针对每个points的标记,数组,长度为 n_points。 If bit 0 is unset, the point is 'off' the curve, i.e., a Bézier control point, while it is 'on' if set. Bit 1 is meaningful for 'off' points only. If set, it indicates a third-order Bézier arc control point; and a second-order control point if unset. If bit 2 is set, bits 5-7 contain the drop-out mode (as defined in the OpenType specification; the value is the same as the argument to the SCANMODE instruction). Bits 3 and 4 are reserved for internal purposes. contours 轮廓信息,数组,长度为 n_contours,给定每一组轮廓的边界索引。例如,第1组轮廓数据是从 索引0 - contours[0],第2组轮廓数据从 索引contours[0]+1 - contours[1],以此类推。 flags A set of bit flags used to characterize the outline and give hints to the scan-converter and hinter on how to convert/grid-fit it. See FT_OUTLINE_XXX.
以字符a为例
n_contours: 2
n_points: 48
Contour[0] = 32
Contour[1] = 47

第一条轮廓的顶点坐标索引为 0 - 32
第二条轮廓的顶点坐标索引为 33 - 47

伪代码
for (int c = 0; c < outline.n_contours; c++ )
{
int i = (c == 0 ? 0 : outline.contours[c-1]+1 );
根据给出的坐标连线;
for (; i<=outline.contours[c] ; i++)
{
坐标( outline.points[i].x, outline.points[i].y );
}
绘线结束;
}
line.png
(3.05 KiB) 已下载 7106 次
但是这里仅仅是直线连接,二次曲线就相对麻烦点了。借用FontForge查看字符a的轮廓(consola.ttf)
FontForge.png
(19.21 KiB) 已下载 7096 次
其中有绿褐色向上箭头的是单个轮廓的起点,圆点和 x 标记分别表示Bezier曲线上的定点和控制点。

在FontForge 字符轮廓编辑界面,右键点击某个顶点->GetInfo 可以查看坐标。
其中实心三角形、方形和实心圆形的坐标在 outlines.points 中已经给出,而空心圆形的坐标则需要
自己计算(空心圆的坐标恰好是两个相邻点的中点)。

参考 Freetype初探 - 了解TrueType Outlines 和 PostScript Outlines
Truetype 字体使用2次贝塞尔曲线和直线线段描绘字体轮廓,2次Bezier曲线分为2个定点和1个控制点

正确的坐标解读方式为:
2次Bezier曲线简写为 B+编号 的形式
编号 x y tags值
=======================================
0 : 805 0 1 起点
1 : 801 135 1 直线点,B1起点
2 : 719 54 0 B1控制点
求出2, 3的中间点,作为B1终点、B2起点
3 : 550 -18 0 B2控制点
4 : 457 -18 1 B2终点,B3起点
5 : 371 -18 0 B3控制点
求出5, 6的中间点,作为B3终点、B4起点
6 : 249 26 0 B4控制点
求出6, 7的中间点,作为B4终点、B5起点
7 : 170 103 0
…… 以此类推
8 : 133 207 0
9 : 133 268 1

从上面可以发现,每出现一个0(tags)都代表一节Bezier曲线的控制点,当出现连续的0的时候,
曲线的定点需要自己通过相邻的点坐标求出。

(表达有不对的地方还请多多指教)
头像
523066680
Administrator
Administrator
帖子: 573
注册时间: 2016年07月19日 12:14
联系:

接2楼,OpenGL2.1 绘制 FreeFype Outlines 轮廓线

帖子 523066680 »

使用 OpenGL 库(2.1) 绘制 FreeFype Outlines 导出的轮廓线

在GetDatafromOutline函数中提取和整理坐标数据,在display函数中绘制。
2次曲线没有自己写函数计算,而是使用gl2.1自带的求值器glMap,只要做一些初始设置并丢3个坐标信息到函数里就可以了。
// Code By: 523066680
// Date: 2016-11

#include <GL/glut.h>
#include <unistd.h>
#include <stdio.h>

#include <ft2build.h>
#include <freetype/ftbbox.h>
#include <freetype/ftoutln.h>
#include FT_FREETYPE_H

#define SIZE_X 500
#define SIZE_Y 500

int winID;
FT_Library library;
FT_Face face;

FT_GlyphSlot slot;
FT_Error error;
FT_Outline outline;

float curves[100][9];
float lines[100][2];

int ci;
int li;
long code = 97;

void GetDatafromOutline(void);
void LoadGlyph(long symbol);

void display(void)
{
int i, j;
// 清理颜色缓冲区
glClear( GL_COLOR_BUFFER_BIT );
glColor3f(1.0, 1.0, 1.0);
glMapGrid1f(20, 0.0, 1.0);

for (int c = 0; c < ci; c++)
{
glMap1f(GL_MAP1_VERTEX_3, 0.0, 1.0, 3, 3, curves[c]);
glEvalMesh1(GL_LINE, 0, 20);
}

glBegin(GL_LINES);
for (int n = 0; n < li; n++)
{
glVertex2fv( lines[n] );
}
glEnd();

glutSwapBuffers();
}

void idle(void)
{
usleep(100000);
glutPostRedisplay();
}

void reshape(int Width, int Height)
{
const float fa = 32.0;
const float half = 1500.0;

glViewport(0, 0, Width, Height); //视口范围
glMatrixMode(GL_PROJECTION); // 投影视图矩阵
glLoadIdentity();
glOrtho(-half, half, -half, half, -10.0, 100.0);
glMatrixMode(GL_MODELVIEW); // 模型视图矩阵
glLoadIdentity();
gluLookAt(0.0,0.0,fa, 0.0,0.0,0.0, 0.0,1.0,fa); //设置观察点
// 观察点, 朝向的坐标, 观察点向上坐标
}

void keypress(unsigned char key, int mousex, int mousey)
{
switch (key)
{
case 'q':
glutDestroyWindow(winID);
exit(0);
break;
case 'Q':
glutDestroyWindow(winID);
exit(0);
break;
case 'a':
code++;
LoadGlyph(code);
GetDatafromOutline();
glutPostRedisplay();
break;
}
}

void init(void)
{
glClearColor(0.0, 0.0, 0.0, 0.0);
glLineWidth( 2.0 );
glPointSize( 2.0 );
glEnable(GL_POINT_SMOOTH);
glEnable(GL_LINE_SMOOTH);
glEnable(GL_MAP1_VERTEX_3);
}

void ftinit(void)
{
char* filename;
filename = "C:/windows/fonts/consola.ttf";

error = FT_Init_FreeType( &library );
error = FT_New_Face( library, filename, 0, &face ); /* create face object */

slot = face->glyph;
}

void LoadGlyph(long symbol)
{
//这里可以是unicode编码值,字体必须支持才行
FT_UInt index = FT_Get_Char_Index(face, symbol);
FT_Error error = FT_Load_Glyph(face,
index,
FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP);
outline = slot->outline;
}

void GetDatafromOutline(void)
{
ci = 0;
li = 0;
int bgn;
int next;

for (int cts = 0; cts < outline.n_contours ; cts++ )
{
bgn = cts == 0 ? 0 : outline.contours[cts-1] + 1;

for (int i = bgn; i <= outline.contours[cts] ; i++)
{
//i==终点时,next回到某个轮廓的起点
next = ( i == outline.contours[cts] ? bgn : i+1 );

if ( outline.tags[i] == 0 )
{
curves[ci][2] = 0.0;
curves[ci][3] = (float)outline.points[i].x; //控制点
curves[ci][4] = (float)outline.points[i].y;
curves[ci][5] = 0.0;
curves[ci][8] = 0.0;

if ( outline.tags[i-1] == 1 ) //1, 0
{
curves[ci][0] = (float)outline.points[i-1].x;
curves[ci][1] = (float)outline.points[i-1].y;
if (outline.tags[next] == 1) //1, 0, 1
{
curves[ci][6] = (float) outline.points[next].x;
curves[ci][7] = (float) outline.points[next].y;
}
else if ( outline.tags[next] == 0 ) //1, 0, 0
{
curves[ci][6] = (float)(outline.points[i].x + outline.points[next].x)/2;
curves[ci][7] = (float)(outline.points[i].y + outline.points[next].y)/2;
}
}
else //0, 0
{
curves[ci][0] = curves[ci-1][6]; //起点为上一段曲线的终点
curves[ci][1] = curves[ci-1][7];
if ( outline.tags[next] == 0 ) //0, 0, 0
{
curves[ci][6] = (float)(outline.points[i].x + outline.points[next].x)/2 ;
curves[ci][7] = (float)(outline.points[i].y + outline.points[next].y)/2 ;
}
else //0, 0, 1
{
curves[ci][6] = (float)outline.points[next].x ;
curves[ci][7] = (float)outline.points[next].y ;
}
}
ci++;
}
else
{
//直线线段
if ( outline.tags[next] == 1 ) //1, 1
{
lines[li][0] = outline.points[i].x;
lines[li][1] = outline.points[i].y;
lines[li+1][0] = outline.points[next].x;
lines[li+1][1] = outline.points[next].y;
li+=2;
}
}
}
}

}


int main( int argc, char** argv )
{

ftinit();
LoadGlyph('m');
GetDatafromOutline();

printf("n_contours: %d \n n_points: %d \n flags: %d\n", outline.n_contours,
outline.n_points,
outline.flags
);

glutInit(&argc, argv);
//显示模式 双缓冲 RGBA
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_MULTISAMPLE );
glutInitWindowSize(SIZE_X, SIZE_Y); //窗口大小
glutInitWindowPosition(200, 200); //位置
winID = glutCreateWindow("Fonts"); //窗口句柄
init();
glutDisplayFunc(display); //显示
glutKeyboardFunc(keypress); //按键事件响应
glutReshapeFunc(reshape); //窗口事件响应
glutIdleFunc(idle); //闲时回调函数
glutMainLoop(); //开始主循环

FT_Done_Face ( face );
FT_Done_FreeType( library );

return 0;
}
curve_a.png
(3.24 KiB) 已下载 7088 次
如果你发现某个中文字体的中文显示有问题
那是因为某些汉字组成的曲线段比较多,超过100条,curves[] 数组大小不够,然后导致操作的地址跑到了lines里面去了
导致线条错乱。(这分明是因为我的代码不好)
头像
523066680
Administrator
Administrator
帖子: 573
注册时间: 2016年07月19日 12:14
联系:

FT_Outline_Get_BBox - 获取某个字形、字符轮廓的包围盒大小

帖子 523066680 »

FT_Outline_Get_BBox - 获取某个字形、字符轮廓的包围盒大小

轮廓范围大小,可以自己计算,但是Freetype库已经提供了相关函数
极简示例:
FT_UInt index = FT_Get_Char_Index(face, 'a');
FT_Error error = FT_Load_Glyph(face,
index,
FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP);
slot = face->glyph;
outline = slot->outline;
FT_Outline_Get_BBox( &outline, &box );
printf("%d, %d, %d, %d\n", box.xMax, box.xMin, box.yMax, box.yMin);
回复

在线用户

正浏览此版面之用户: 没有注册用户 和 0 访客