FreeType 用法
Freetype是一个跨平台、开源的字体渲染器,网上很多文章介绍,本人就不啰嗦了。本文重点在于实现文章标题所属的各种效果,不是Freetype的基本使用方法介绍文档,所以对于Freetype不熟悉的同学们请先学习下Freetype的基本用法,才可以使用本文中所提及的方法。
用FreeType实现矢量字体的粗体、斜体、描边、阴影效果不是一件容易的事,本人认为皆因Freetype的接口太过于底层化,Freetype没有对其进行上层包装,所以要实现这些对于软件/游戏来说的基本效果,都是件挺麻烦的事情。
1. 加粗
加粗可以使用FreeType中的一个API来实现FT_Outline_Embolden,但是这个API不支持水平垂直方向加粗量的分别设置,所以,需要参照FT_Outline_Embolden的实现重新编写一个函数,GDI++已经做了这个事情,引用它的代码:
FT_Error Old_FT_Outline_Embolden( FT_Outline* outline, FT_Pos strength )
{
FT_Vector* points;
FT_Vector v_prev, v_first, v_next, v_cur;
FT_Angle rotate, angle_in, angle_out;
FT_Int c, n, first;
FT_Int orientation;
if ( !outline )
return FT_Err_Invalid_Argument;
strength /= 2;
if ( strength == 0 )
return FT_Err_Ok;
orientation = FT_Outline_Get_Orientation( outline );
if ( orientation == FT_ORIENTATION_NONE )
{
if ( outline->n_contours )
return FT_Err_Invalid_Argument;
else
return FT_Err_Ok;
}
if ( orientation == FT_ORIENTATION_TRUETYPE )
rotate = -FT_ANGLE_PI2;
else
rotate = FT_ANGLE_PI2;
points = outline->points;
first = 0;
for ( c = 0; c < outline->n_contours; c++ )
{
int last = outline->contours[c];
v_first = points[first];
v_prev = points[last];
v_cur = v_first;
for ( n = first; n <= last; n++ )
{
FT_Vector in, out;
FT_Angle angle_diff;
FT_Pos d;
FT_Fixed scale;
if ( n < last )
v_next = points[n + 1];
else
v_next = v_first;
/* compute the in and out vectors */
in.x = v_cur.x - v_prev.x;
in.y = v_cur.y - v_prev.y;
out.x = v_next.x - v_cur.x;
out.y = v_next.y - v_cur.y;
angle_in = FT_Atan2( in.x, in.y );
angle_out = FT_Atan2( out.x, out.y );
angle_diff = FT_Angle_Diff( angle_in, angle_out );
scale = FT_Cos( angle_diff / 2 );
if ( scale < 0x4000L && scale > -0x4000L )
in.x = in.y = 0;
else
{
d = FT_DivFix( strength, scale );
FT_Vector_From_Polar( &in, d, angle_in + angle_diff / 2 - rotate );
}
outline->points[n].x = v_cur.x + strength + in.x;
//伀偙傟傪僐儊儞僩傾僂僩偟偨偩偗
//outline->points[n].y = v_cur.y + strength + in.y;
v_prev = v_cur;
v_cur = v_next;
}
first = last + 1;
}
return FT_Err_Ok;
}
// 垂直加粗
FT_Error Vert_FT_Outline_Embolden( FT_Outline* outline, FT_Pos strength )
{
FT_Vector* points;
FT_Vector v_prev, v_first, v_next, v_cur;
FT_Angle rotate, angle_in, angle_out;
FT_Int c, n, first;
FT_Int orientation;
if ( !outline )
return FT_Err_Invalid_Argument;
strength /= 2;
if ( strength == 0 )
return FT_Err_Ok;
orientation = FT_Outline_Get_Orientation( outline );
if ( orientation == FT_ORIENTATION_NONE )
{
if ( outline->n_contours )
return FT_Err_Invalid_Argument;
else
return FT_Err_Ok;
}
if ( orientation == FT_ORIENTATION_TRUETYPE )
rotate = -FT_ANGLE_PI2;
else
rotate = FT_ANGLE_PI2;
points = outline->points;
first = 0;
for ( c = 0; c < outline->n_contours; c++ )
{
int last = outline->contours[c];
v_first = points[first];
v_prev = points[last];
v_cur = v_first;
for ( n = first; n <= last; n++ )
{
FT_Vector in, out;
FT_Angle angle_diff;
FT_Pos d;
FT_Fixed scale;
if ( n < last )
v_next = points[n + 1];
else
v_next = v_first;
/* compute the in and out vectors */
in.x = v_cur.x - v_prev.x;
in.y = v_cur.y - v_prev.y;
out.x = v_next.x - v_cur.x;
out.y = v_next.y - v_cur.y;
angle_in = FT_Atan2( in.x, in.y );
angle_out = FT_Atan2( out.x, out.y );
angle_diff = FT_Angle_Diff( angle_in, angle_out );
scale = FT_Cos( angle_diff / 2 );
if ( scale < 0x4000L && scale > -0x4000L )
in.x = in.y = 0;
else
{
d = FT_DivFix( strength, scale );
FT_Vector_From_Polar( &in, d, angle_in + angle_diff / 2 - rotate );
}
//outline->points[n].x = v_cur.x + strength + in.x;
//仾偙傟傪僐儊儞僩傾僂僩偟偨偩偗
outline->points[n].y = v_cur.y + strength + in.y;
v_prev = v_cur;
v_cur = v_next;
}
first = last + 1;
}
return FT_Err_Ok;
}
// 新的加粗函数
FT_Error New_FT_Outline_Embolden( FT_Outline* outline, FT_Pos str_h, FT_Pos str_v )
{
if ( !outline ) return FT_Err_Invalid_Argument;
int orientation = FT_Outline_Get_Orientation( outline );
if ( orientation == FT_ORIENTATION_NONE )
if ( outline->n_contours ) return FT_Err_Invalid_Argument;
Vert_FT_Outline_Embolden( outline, str_v );
Old_FT_Outline_Embolden( outline, str_h );
return FT_Err_Ok;
}
// 让一个字体槽加粗,并且填充其他的大小属性
void New_GlyphSlot_Embolden( FT_GlyphSlot slot , const Vector2Float &embolden)
{
if(embolden == Vector2Float::ZERO)
return;
FT_Library library = slot->library;
FT_Face face = slot->face;
FT_Error error;
FT_Pos xstr = embolden.x, ystr = embolden.y;
if ( slot->format != FT_GLYPH_FORMAT_OUTLINE &&
slot->format != FT_GLYPH_FORMAT_BITMAP )
return;
if ( slot->format == FT_GLYPH_FORMAT_OUTLINE )
{
FT_BBox oldBox;
FT_Outline_Get_CBox(&slot->outline , &oldBox);
error = New_FT_Outline_Embolden( &slot->outline, xstr , ystr);
if ( error )
return;
FT_BBox newBox;
FT_Outline_Get_CBox(&slot->outline , &newBox);
xstr = (newBox.xMax - newBox.xMin) - (oldBox.xMax - oldBox.xMin);
ystr = (newBox.yMax - newBox.yMin) - (oldBox.yMax - oldBox.yMin);
}
else if ( slot->format == FT_GLYPH_FORMAT_BITMAP )
{
xstr = FT_PIX_FLOOR( xstr );
if ( xstr == 0 )
xstr = 1 << 6;
ystr = FT_PIX_FLOOR( ystr );
error = FT_Bitmap_Embolden( library, &slot->bitmap, xstr, ystr );
if ( error )
return;
}
if ( slot->advance.x )
slot->advance.x += xstr;
if ( slot->advance.y )
slot->advance.y += ystr;
slot->metrics.width += xstr;
slot->metrics.height += ystr;
slot->metrics.horiBearingY += ystr;
slot->metrics.horiAdvance += xstr;
slot->metrics.vertBearingX -= xstr / 2;
slot->metrics.vertBearingY += ystr;
slot->metrics.vertAdvance += ystr;
if ( slot->format == FT_GLYPH_FORMAT_BITMAP )
slot->bitmap_top += ystr >> 6;
}
加粗:对于MONO渲染方式,使用FreeType提供的FT_Bitmap_Embolden函数。如果使用Default渲染,则使用FT_Outline_Embolden加粗。
下划线:绘制下划线时,先通过face->size->metrics.y_ppem来获得该字库字符高,face是一个已经实例化的FT_Face。接着,绘制字符串时,通过bitmapinfo->root.advance.x>>16获得每个打印字符的宽,并累加,最后获得一个字符串像素长,bitmapinfo是一个FT_BitmapGlyph对象。接着就可以绘制想要的下划线了。
斜体:如果是Default渲染,可以使用FT_Set_Transform函数。但如果是MONO渲染,很不幸没有现成的API用了。所以,这里要自己变通一下了。我这里使用SetPixel为例说明一下,首先我这边给定一个字符倾斜角度为45度,那个可以得到一个线性函数y=x。下面是MONO渲染斜体伪代码:
for(y=0;y<charHeight;y++)
{
iItalicWidth=(charHeight-y); //计算倾斜像素个数
for(x=0;x<charWidth;x++)
{
charData=GetCharData(x,y);
if(charData)
SetPixel(x+iItalicWidth,y,color::Black);
}
}
2 斜体
斜体在FreeType中可以通过矩阵变换来实现,只要把矩阵设置成一个切边矩阵就可以了,方法如下:
float lean = 0.5f;
FT_Matrix matrix;
matrix.xx = 0x10000L;
matrix.xy = lean * 0x10000L;
matrix.yx = 0;
matrix.yy = 0x10000L;
FT_Set_Transform( face, &matrix, 0 );
3. 描边
网上有不少文章说描边其实很简单,就是上下左右各移动一个像素渲染一次,最后在中间再渲染一次就可以了。但是,这种方法只对于位图字体有效,对于矢量字体,效果就不好了,特别是大字体,1个像素只是很细的边界而已,对于很小的字体,1像素又显得太大。
这里提供另一种实现方案,使用的是Freetype的API:
4. 使用FT_Stroker_New创建一个笔触
5. FT_Stroker_Set设置笔触为描边
6. 把Load后的glyph通过FT_Glyph_Copy拷贝一份出来
7. 对这个拷贝出来的glyph使用FT_Glyph_StrokeBorder设置成描边渲染
8. 使用FT_Outline_Render渲染这个描边的glyph,渲染前要设置FT_Raster_Params参数成:
memset(¶ms, 0, sizeof(params));
params.flags = FT_RASTER_FLAG_AA | FT_RASTER_FLAG_DIRECT;
params.gray_spans = RasterCallback;
9. 在回调函数RasterCallback中实现像素化到位图中
10. 对原来的glyph执行8操作,在回调函数RasterCallback中实现像素化到位图中,像素化过程使用alphablend的方式绘制上去
11. 把位图渲染到屏幕上或保存到文件中
4. 阴影
阴影的实现就比较简单了,只要一个个像素偏移后多渲染几次就可以了,再次不多说。
字体轮廓:首先使用FreeType创建两个字体库对象,加载同一个字体,设置字体大小也相同。然后把其中一个字体字模进行加粗效果。接着先绘制那个加粗的字体,最后绘制另一个字体就OK了。当然有时可能要自己去微调一两个像素。
字体阴影:比字体轮廓实现更简单。用FreeType加载一个字库,然后用一个较浅的颜色绘制一个字符串。然后偏移几个像素(按自己要求调)绘制一个颜色比较深的字符串,就大功告成了。有时给底部那个字符串加一个倾斜,效果看上去也不错
1. 初始化FT lib
FT_Library library; /* handle to library */
FT_Face face; /* handle to face object */
// 1. Init the library
if ( FT_Init_FreeType( &library ) )
{
MessageBox(_T("Init freetype library failed."), _T("Error"), MB_OK | MB_ICONERROR);
FT_Done_FreeType(library);
return;
}
int nface = 0;
//nface = 1;
//pathstring是TTF文件的路径
FT_Error error = FT_New_Face(
library,
pathstring,
nface,
&face );
if ( error == FT_Err_Unknown_File_Format )
{
MessageBox(_T("Font format not supported."), _T("Error"), MB_OK | MB_ICONERROR);
FT_Done_FreeType(library);
return;
}
else if ( error )
{
MessageBox(_T("Font face open failed."), _T("Error"), MB_OK | MB_ICONERROR);
FT_Done_FreeType(library);
return;
}
2. 获取font face信息
AddMessage(_T("Face information:"));
//glyph数量
sMessage.Format(_T(" Totally %d glyphs."), face->num_glyphs);
AddMessage(sMessage);
//每EM unit数量
sMessage.Format(_T(" %d uints per EM."), face->units_per_EM);
AddMessage(sMessage);
//char map数量
sMessage.Format(_T(" %d char maps."), face->num_charmaps);
AddMessage(sMessage);
//fix size
//这个对于汉字很重要,fix size对于小字体显示很有帮助
sMessage.Format(_T(" %d fixed sizes:"), face->num_fixed_sizes);
if(face->available_sizes)
{
for(int ii = 0 ; ii < face->num_fixed_sizes ; ii++)
{
sTemp.Format(_T(" %d"), face->available_sizes[ii].size / 64);
sMessage += sTemp;
}
}
AddMessage(sMessage);
int iUnderlinePos = 0;
//下划线位置
if( FT_IS_SCALABLE(face) )
{
iUnderlinePos = FT_MulFix( face->underline_position , face->size->metrics.y_scale);
iUnderlinePos >>= 6;
sMessage.Format(_T(" underline position:%d"), iUnderlinePos);
AddMessage(sMessage);
}
3. 设置参数,为获取glyph image做准备
//按pixel大小设置字体尺寸
FT_Set_Pixel_Sizes(face, m_iFontHeight, m_iFontHeight);
//检查是否SCALABLE
if( !FT_IS_SCALABLE(face) )
{
//Not a scalable font (Truetype or Type1)
ASSERT(0);
}
//当需要竖排文字时检查是否支持
if( !m_bHorizontal && !FT_HAS_VERTICAL(face))
{
MessageBox(_T("This font doesn't support vertical layout!"), _T("Error"), MB_OK | MB_ICONERROR);
// Do the clean up
FT_Done_Face( face );
FT_Done_FreeType(library);
return;
}
//下面这个matrix用于设置斜体字
FT_Matrix matrix; /* transformation matrix */
matrix.xx = 1 << 16;
matrix.xy = 0x5800;
matrix.yx = 0;
matrix.yy = 1 << 16;
4. 获取glyph,并设置格式
//curchar是该字符的unicode编码
FT_Load_Char(face, curchar, FT_LOAD_DEFAULT);
if(m_bBold)
{//加粗
int strength = 1 << 6;
FT_Outline_Embolden(&face->glyph->outline, strength);
}
if(m_bItalic)
{//斜体
/* set transformation */
//FT_Set_Transform( face, &matrix, 0 );
FT_Outline_Transform(&face->glyph->outline, &matrix);
}
5. Render glyph,准备拷贝glyph image
ftResult = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
if (ftResult)
{
continue;//因为这是在一个循环中
}
// 获取该文字整个宽度,包含跨距
FT_Int advance = (face->glyph->advance.x >> 6 );// + pixelwordspacing;
// 得到渲染后bitmap buffer指针
unsigned char* buffer = face->glyph->bitmap.buffer;
if (!buffer)
{
continue;
}
6. 拷贝glyph image
switch (face->glyph->bitmap.pixel_mode)
{
case FT_PIXEL_MODE_GRAY:
{
for(int k = 0; k < face->glyph->bitmap.width; k++ )
{
pixelclr = buffer[j * face->glyph->bitmap.pitch + k];
// 可以使用pixelclr作为alpha值
// pDest是显示用的image内存指针
*pDest++= pixelclr;
}
break;
case FT_PIXEL_MODE_MONO:
//很多人不知道这个对应的就是fix size时的图片格式,每个bit对应一个点,非黑即白
for(int k = 0; k < face->glyph->bitmap.width; k++ )
{
pixelclr = (src [k / 8] & (0x80 >> (k & 7))) ? 0xFFFFFFFF : 0x00000000;
// pDest是显示用的image内存指针
*pDest++= pixelclr;
}
break;
default:
//throw InvalidRequestException("Error - unsupported pixel mode");
break;
}
7. 关于竖排文字
//
ftResult = FT_Load_Char( face, curchar, FT_LOAD_DEFAULT | FT_LOAD_VERTICAL_LAYOUT);
FreeType 用法相关推荐
- freetype用法
freetype用法 ` 文章目录 freetype用法 0.实现 1.变量定义 2.lcd操作获取屏幕信息 3.freetype初始化 4.绘画 1.字形度量 2.类 1.FT 中的面向对象 2.F ...
- 使用redis实现缓存_用下一个js实现一个简单的redis缓存
使用redis实现缓存 For most websites, the changing pieces don't actually vary that often. That immutability ...
- FreeType简介及在vs2010的编译使用
FreeType库是一个开源.高质量.可扩展.可定制.可移植的字体引擎,它提供统一的接口来访问多种字体格式文件,包括点阵字.TrueType.OpenType.Type1.CID.CFF.Window ...
- Harfbuzz API 基本用法
[Harfbuzz](http://harfbuzz.org/ 是一个 OpenType 文本整形引擎.当前的 Harfbuzz 代码库,之前被称为 harfbuzz-ng,版本号为 1.x.x,它是 ...
- FreeType, FFmpeg, SDL, 图像处理软件, Mac OS X, Objective-C
http://antkillerfarm.github.io/ FreeType使用指南 FreeType是一套跨平台的字体文件编程开发包.它的官网是www.freetype.org,你可以到这个网站 ...
- Freetype的使用
为了在公司的游戏引擎中添加truetype支持,捣鼓了一段时间freetype库. 1. 基本用法 freetype的编译使用就不细说了,非常的简单,网上的说明教程很多. 基本代码基本如下: 初始化f ...
- 使用FreeType实现矢量字体的粗体、斜体、描边、阴影效果
使用FreeType实现矢量字体的粗体.斜体.描边.阴影效果 原文 http://www.cppblog.com/mybios/archive/2009/08/01/91869.html 使用Free ...
- 基于Qt的FreeType字体轮廓解析
一.本文目的 以前的文档中.详细的介绍了FreeType开源字体引擎库的基础知识.基本用法.但并未详细的阐明在TurboCG中.是如何解析出一个文字的轮廓的,本文集中阐述.怎么样使用FreeType开 ...
- 【Golang画图】2D渲染绘图库gg的概念与用法详解(一)
文章目录 概述 基本使用 基本概念 基本底层操作 具体操作: 示例1:画圆 示例2:画带边框的矩形 示例3:贴图片文件 示例4:贴文字 示例5:半透明+缩放贴图 备注 概述 最近的项目中需要用代码来手 ...
最新文章
- 学习JavaScript数据结构与算法(一):栈与队列
- app.vue中引用图片src=“../assets/logo.png“报错未找到图片
- C语言_结构体与共用体
- 2-1为什么选择Pytorch
- 移植ubuntu14.04根文件系统至beaglebone开发板探索
- SpringBoot连接远程云服务器的Redis并且让Redis后台运行
- java字符如何向float转换_java – 将float转换为字符串分数表示
- Docker - 基于NVIDIA-Docker的Caffe-GPU环境搭建
- 石墨烯的晶格和能带结构
- 百度竞价推广地域是如何选择的?
- vue报错:vue.js:634 [Vue warn]: Cannot find element: #app
- 12个免费logo生成器
- acml会议级别_人工智能领域的顶级学术会议大全(二)
- JavaWeb企业实战项目(一):环境搭建-用户注册-邮件发送
- 华为手机怎么语音服务器,原来华为手机实现文字转语音这么简单!今天才知道,真是绝了...
- windows下的可执行程序可以再linux下运行吗?
- Android读取中文文件乱码解决方法
- 群晖php7.0,群晖新版操作系统DSM 7.0下载
- GCC中 -I、-L、-l 选项的作用
- codeforces Cthulhu 简单图论
热门文章
- 华为鸿蒙P10plus,华为P10深度评测:徕卡双摄拍照逆天!
- Android--经典蓝牙(文件传输--socket通信)
- verilog——74HC85四位数值比较器并扩展为16位数值比较器
- MFC中单文档程序框架
- with open()as filename
- Python—reverse()和reversed()方法介绍
- android chrome无法运行,Android 测试 Chrome 浏览器能正常启动 Chrome 浏览器,但是不能进行操作,求大神!!...
- matlab pwm整流仿真
- 分布式系统与网络分区
- 彻底了解 suid, sgid ,sticky权限