[福利]国内首篇利用freetype的跨平台truetype字体真正轮廓(非位图)获取(带完整qt工程代码)-秒杀GetGlyphOutline

来源:互联网 发布:竹纤维集成墙 知乎 编辑:程序博客网 时间:2024/05/19 12:36

前言

最近在做一款激光打标控制的产品,我的思路是将所有的图元矢量化,但是当做到文字矢量化的时候,真是让我想破了脑袋,后来搜索得知了GetGlyphOutline,就是一个WINAPI,众所周知,WINAPI大都需要一个HDC,但是我使用的Qt,这样就出现了几个问题:

  • Qt中的控件有些是直接渲染的,很难直接获取控件句柄-
  • 不跨平台,以后这一块还是要重写
  • VC的各种变量类型写在Qt里面,调试起来真是一团糟

做了些实验以后,发现这种方法实在是缘木求鱼,然后轻易地搜索到了freeType这一款优秀的字体引擎,下面我们就开始对freetype的探索。

所需基本知识点

trueType字体的一些基本概念

  • 字体(font):不同字符图像的集合

  • 字体外观(font face):一个外观对应一个(.ttf)文件,但是一个字体家族可能占用多个字体文件,因为它包括多种外观,比如字体族Arial,它包括两种外观,于是就有 arial.ttf对应Arial Regular外观,ariali.ttf对应Arial Italic外观,我们习惯把Arial Regular也称为一种字体,实际上它只是一种字体外观

  • 字体文件的基本结构:一般里面有一个或多个字符图(charmap),不同的字符图一般标识不同的平台,所以在一种平台上一般只有一种字符图,这个字符图可以宏观简单的理解为一个key-value,key-字符索引一般就是字符对应编码的编码值,value(字符构成)在truetype一般是对其矢量图形的描述,我们只要将字符编码对应的矢量图形描述得到,就可以进行随心所欲的处理了

trueType字体的基本构成

首现说一下字体轮廓,拿我自己解析出来的一个例子来说吧(这是俺的大名:)):
解析出来的源字体
渲染出来的矢量还是不错的!
轮廓线就是字体轮廓中一条条的封闭曲线:
标注轮廓线的元
红色箭头标注的就是字体轮廓线,共有两条;每条轮廓线又由其他直线或曲线组成:

  • 直线
  • 二次Bezier曲线
  • freetype官方说明字体曲线可能包含有(三次Bezier曲线),但经过我的实验以及其他官方资料,truetype字体并不含有三次Bezier曲线

besier曲线定义

由于此处只用到一次贝塞尔(有界直线)和二次贝塞尔曲线
给定点P0、P1,线性bezier曲线只是一条两点之间的直线。这条线由下式给出,且其等同于线性插值。
一次bezier曲线表达式
二次方bezier曲线的路径由给定点P0、P1、P2的函数B(t)追踪:
二次bezier曲线表达式
按照上述插值规则,如果t分割的足够密,就可以得到平滑的曲线

freetype对trueType的解析

解析基本步骤

Created with Raphaël 2.1.2开始freetype字体初始化设置字符编码方式获取字符对应的编码值查charmap表获取编码值的索引根据索引获取轮廓描述对获取到的轮廓数据进行补偿相关渲染和处理结束

freetype字体库初始化(省略了变量声明)

int  error = FT_Init_FreeType( &library );    if(error)    {        printf("load freetype errror!");    }    error = FT_New_Face(library,fontFilePath.toStdString().c_str() ,0,&face);    if ( error == FT_Err_Unknown_File_Format )        {        printf("FT_Err_Unknown_File_Format");    }    else if(error)    {        printf(" another error code means that the font file could not  or simply that it is broken...");    }    error = FT_Set_Pixel_Sizes(face, /* handle to face object */                                      0, /* pixel_width */                                   8 );/* pixel_height */    if(error)    {        printf("char size set error");    }

设置字体编码方式

只需一句代码

FT_Select_Charmap(face,FT_ENCODING_UNICODE);

获取字符编码值

wchar_t charX= L'元';

对字体轮廓进行解析

这里我们需要先对FT_Outline这个freetype内置结构体做一个说明:
FT_Outline

  • n_points:轮廓中的点数
  • n_contours 轮廓中轮廓线数
  • points 点坐标数组
  • contours 轮廓线端点索引数组
  • tags 点标记数组

注意:下方全程高能
这里,points是一个FT_Vector记录数组的指针,用来存储每个轮廓点的向量坐标。它表示为一个象素1/64,也叫做26.6固定浮点格式。
contours是一组点索引,用来划定轮廓的轮廓线。例如,第一个轮廓线总是从0点开始,以contours[0]点结束。第二个轮廓线从contours[0]+1点开始,以contours[1]结束,等等。
注意,每条轮廓线都是封闭的,n_points应该和contours[n_controus-1]+1相同。最后,tags是一组字节,用来存放每个轮廓的点标记。
从这儿,大家可能就会感觉以下的步骤很麻烦了!是的,至少会需要两层循环,这还不算完,最难处理的是下面这一堆规定:

轮廓内部点规则描述

  • 每条弧由一系列起点、终点和控制点描述,轮廓的每个点有一个特定的标记,表示它用来描述一个线段还是一条弧。这个标记可以有以下值:
  • FT_Curve_Tag_On 当点在曲线上,这对应线段和弧的起点和终点。其他标记叫做“Off”点,即它不在轮廓线上,但是作为Bezier弧的控制点。
  • FT_Curve_Tag_Conic 一个Off点,控制一个conic Bezier弧
  • FT_Curve_Tag_Cubic 一个Off点,控制一个cubic Bezier弧
  • 下面的规则应用于将轮廓点分解成线段和弧 z 两个相邻的“on”点表示一条线段;
  • 弧z 一个conic Off(二次bezier曲线控制点)在两个on点之间表示一个conic Bezier(二次bezier)弧,off点是控制点,on点是起点和终点;
  • 两个相邻的cubic off(三次bezier曲线控制点)点在两个on点之间表示一个cubic Bezier(三次bezier)弧,它必须有两个cubic控制点和两个on
    点。
  • 两个相邻的conic off(二次bezier曲线控制点)强制在它们正中间创建一个虚拟的on点(坐标为两者中点)。这大大方便定义连续的conic弧。TrueType规范就是这么定义的。

轮廓端点规则描述

  • 如果轮廓的首尾点均为FT_Curve_Tag_On,则不需做任何处理
  • 如果轮廓首点为FT_Curve_Tag_Conic,尾点为FT_Curve_Tag_On,就将尾点作为第一条bezier曲线的首点
  • 如果轮廓首点为FT_Curve_Tag_On,尾点为FT_Curve_Tag_Conic,就将首点作为最后一条bezier曲线的尾点
  • 如果首尾点均为FT_Curve_Tag_Conic,则取两者平均值分别作为第一条bezier曲线的首点和最后一条曲线的尾点。

规则总结

这一串规则看起来非常复杂晦涩,其实总结一下就是,每个轮廓点就是是一个’圆环’的点集,当出现相邻的bezier控制点后,就补偿一个中值。最后,我们’拆环’就可以了

解析算法描述

Created with Raphaël 2.1.2获取outline中的各个值单个轮廓线首尾索引值获取首端点补偿中点连续的控制点的中值补偿尾端点补偿拆分成一段段独立bezier曲线结束

单个轮廓线首尾索引值获取

Created with Raphaël 2.1.2单个轮廓线首尾索引值获取第几条轮廓线是否是第0条 ?索引值 0~outline->contours[0]处理完毕索引值 outline->contours[contourIndex -1] +1~outline->contours[contourIndex]yesno

首尾端点补偿

按照上述规则即可,具体看附件工程

中值补偿

也是按照上述规则

很多东西看代码比较直接,附上大家最想要的完整工程,记得改一下pro文件里面的lib目录路径:
戳我下载源码

2 0
原创粉丝点击