最近公司需要在OpenGL上绘制文字,官网和各大网站均只有C++版本。而公司使用的卷轴控件是通过C#版本的OpenTK绘制,所以必须要通过C#层调用C++ P/Invoke接口获取文字图片生成纹理。
现在我准备介绍我如何一步一步完成FreeType封装成C#可以调用的动态链接库。
环境准备 FreeType+CMake
1、FreeType 官网下载其中一个FreeType库
我下载的是ft261版本
2、CMake下载
由于FreeType下载没有sln文件,无法通过VS打开,所以有必要的话下载一个CMake
CMake 这个很简单,我之前没用过也很容易上手
然后打开CMake
选择FreeType 所在的文件夹,点击Generate按钮即可
这样,我们在FreeType文件夹下就生成了带有sln的文件。
双击可以运行C++环境的VS版本
3、freetype.lib 的生成
双击打开 freetype.sln文件
右键freetype项目,点到属性页
分别将常规中的 配置类型 选择为静态库(lib);
高级中的高级属性下的目标文件扩展名选择为.lib
右键FreeType进行编译
这样,在红色框标出的位置就有了一个freetype.lib文件,这样,我们Freetype的环境就准备完成了。
Freetype P/Invoke库的开发
1、打开VS,新建一个动态链接库(DLL)项目,并命名为FTFont
创建好的FTFontDll 动态链接库项目
2、右键FTFontDll弹出属性页
在包含目录这一行,选择刚才FT261 Freetype目录下的所在的include文件夹,把所需要的FreeType头文件包含进来。
在链接器->输入 附加依赖项 中把在上几步生成的freetype.lib包含进来
右键编译。这样,一个引用原始FreeType库 的 用来给C# 使用的FTFont.dll 动态链接库就这样生成好了。
1、FTFontDLL.dll 实现调用FreeType方法生成文字
1、第一步,先编写导出头文件
由于生成FreeType大致需要以下几个步骤
1) FT_Library library;
FT_Face face;
a、FT_Init_FreeType(&library); //初始化FT_Library library
b、FT_New_Face(library, ttfPath, 0, &face); //初始化FT_Face face
c、FT_Set_Char_Size(face, 50 * 64, 0, 100, 0); //设置查出来的字体大小
d、FT_Set_Pixel_Sizes(face, 0, fontSize); //设置查出来的字体生成的像素大小,像素越大, 字体越清晰
2)获取字符
//设置获取字符的格式,正常情况下,我们都需要中文,所以设置ft_encoding_unicode
FT_Select_Charmap(face, ft_encoding_unicode);
//获取字符返回值设置在FT_Face类型的glyph变量中,该类型有该字体所有的信息
FT_Load_Char(face, textItem, FT_LOAD_RENDER)
textItem : 如果是中文,传入wchar_t 因为中文是两个字符,如果中英文混搭也一样用两字符方便
如果在确定的情况下只有英文。传入char。
好了,以上就是FreeType常用的几个函数。先有这些概念,后面封装需要导出的dll就会清楚很多。
2、编写FTFontdll导出函数
从上面的对FreeType 函数简单说明,我们大致可以知道获取Freetype字符的bitmap数据只需要以下几步
1、初始化函数
2、传入字符串,循环字符串获取各个字符串的bitmap信息
3、释放Freetype字体
那么,到这里,我们就可以简单的先把FTFont.dll的头文件函数给写出来了
/// <summary>
/// 初始化
/// </summary>
/// <param name="ttfPath">字体文件库的路径</param>
/// <param name="fontSize">字体的清晰度</param>
/// <returns></returns>
bool FTFontSDK_Init(const char* ttfPath,unsigned int fontSize);
/// <summary>
/// 设置字体的颜色
/// </summary>
/// <param name="R">Color的R分量0-255</param>
/// <param name="G">Color的G分量0-255/param>
/// <param name="B">Color的B分量0-255</param>
/// <returns></returns>
void FTFontSDK_SetColor(unsigned char R, unsigned char G, unsigned char B);
/// <summary>
/// 通过字符串获取改字符串在Freetype字体库中的bitmap信息
/// </summary>
/// <param name="text">传入的字符串</param>
/// <param name="FontStruct">返回给C# 的结构体信息指针</param>
/// <returns></returns>
bool FTFontSDK_Get(const wchar_t* text, void* FontStruct);
///释放Freetype的资源
bool FTFontSDK_Release();
注意:如果导出到外部可以调用的dll,特别是C#,则需要添加如下定义
#ifdef FTFONTDLL_EXPORTS
#define FTFONTDLL_API __declspec(dllexport)
#else
#define FTFONTDLL_API __declspec(dllimport)
#endif
好了,头文件已经编写好了,现在我们来编写函数实现
FTFONTDLL_API bool FTFontSDK_Init(const char* ttfPath,unsigned int fontSize);
FTFONTDLL_API bool FTFontSDK_Init(const char* ttfPath, unsigned int fontSize)
{
FT_Init_FreeType(&library);//初始化library
FT_New_Face(library, ttfPath, 0, &face);//初始化字体路径,将ttf文件导出成face
FT_Set_Char_Size(face, 50 * 64, 0, 100, 0);//初始化字体路径
FT_Set_Pixel_Sizes(face, 0, fontSize);//设置字体像素值
return true;
}
结构体信息
struct fontStruct
{
public :
fontStruct();
unsigned int width;
unsigned int height;
unsigned int xMin;
unsigned int xMax;
unsigned int yMin;
unsigned int yMax;
unsigned int bearingY;
unsigned int bearingX;
unsigned int origin;
unsigned int advance;
unsigned char* bitmapBuffer;
};
FTFONTDLL_API bool FTFontSDK_Get(const wchar_t* text, void* FontStruct);
FTFONTDLL_API FTFONTDLL_API bool FTFontSDK_Get(const wchar_t* text, void* FontStruct)
{
FT_Select_Charmap(face, ft_encoding_unicode);//设置编码格式
fontStruct* fontStuct = (fontStruct*)FontStruct;//获取外部传入的结构体
int maxWidth = 0; //由于每个字体的大小不一致,所以如果要把成串字符串输出成一个大的bitmap的话就需要绘制最大宽度
int maxHeight = 0;//同理,最大高度
int maxAscent = 0;//由于每个字体符号对上边沿的距离不一致,会导致输出的字体不好看
int maxDescent = 0;//由于每个字体符号对上边沿的距离不一致,会导致输出的字体不好看
size_t length = wcslen(text);
for (int i = 0; i < length; i++)
{
wchar_t textItem = text[i];
fontStruct _fontStuct;
if (FT_Load_Char(face, textItem, FT_LOAD_RENDER))//获取字符
{
std::cout << "ERROR::FREETYTPE: Failed to load Glyph" << std::endl;
return false;
}///获取字符的第二种方法,目标就是为了获取对应字体的glyph类型
/*FT_Load_Glyph(face, FT_Get_Char_Index(face, textItem), FT_LOAD_DEFAULT);
FT_Glyph glyph;
FT_Get_Glyph(face->glyph, &glyph);
FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1);
FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph)glyph;*/
_fontStuct.width = face->glyph->bitmap.width;
_fontStuct.height = face->glyph->bitmap.rows;
maxWidth += _fontStuct.width;
maxHeight = _fontStuct.height > maxHeight ? _fontStuct.height : maxHeight;FT_GlyphSlot glyphSlot = face->glyph;
if (face->glyph->bitmap_top > (int)maxAscent) {
maxAscent = face->glyph->bitmap_top;
}
if ((glyphSlot->metrics.height >> 6) - face->glyph->bitmap_top > (int)maxDescent) {
maxDescent = (glyphSlot->metrics.height >> 6) - face->glyph->bitmap_top;
}
}
maxHeight = maxAscent + maxDescent;
fontStuct->width = maxWidth;
fontStuct->height = maxHeight;//上面那个循环是为了获取字符串中所有字符的最大宽高,获取到了之后,初始化一个字节数组
//下面这一大段是把各个字体的bitmap合并成一张大的bitmap
BYTE* totalBitmapBuffer = new BYTE[maxWidth * maxHeight];
int MergeWidth = 0;
int yOffset = 0;
for (int j = 0; j < length; j++)
{
wchar_t textItem = text[j];if (FT_Load_Char(face, textItem, FT_LOAD_RENDER))
{
std::cout << "ERROR::FREETYTPE: Failed to load Glyph" << std::endl;
return false;
}
yOffset = maxAscent - face->glyph->bitmap_top;
fontStruct tempFontStuct;
tempFontStuct.width = face->glyph->bitmap.width;
tempFontStuct.height = face->glyph->bitmap.rows;
tempFontStuct.bitmapBuffer = face->glyph->bitmap.buffer;int Height = maxHeight > tempFontStuct.height ? tempFontStuct.height : maxHeight;
for (int HeightIndex = 0; HeightIndex < Height; HeightIndex++)
{
memcpy(totalBitmapBuffer + maxWidth * (HeightIndex+ yOffset) + MergeWidth, tempFontStuct.bitmapBuffer + tempFontStuct.width * HeightIndex, tempFontStuct.width);
}
MergeWidth += tempFontStuct.width;
}//如果需要把Freetype生成的黑白图转为后续可以自定义的彩色图,则需要如下转换
BYTE* totalBitmapBufferRgba = new BYTE[maxWidth * maxHeight * 4];
Bitmap8To32(totalBitmapBuffer, totalBitmapBufferRgba, maxWidth, maxHeight);
delete totalBitmapBuffer;
fontStuct->bitmapBuffer = totalBitmapBufferRgba;
return true;
}
//将Freetype字体转成32位RGBA格式的彩色bitmap
BOOL Bitmap8To32(BYTE* srcImage, BYTE* dstImage, LONG imageWidth, LONG imageHeight)
{
LONG lLineBytes32 = imageWidth * 4;
int j;
for (int i = 0; i < imageHeight; i++)
{
for (j = 0;j < imageWidth; j++)
{
BYTE gray = *(srcImage + imageWidth * i + j);
*(dstImage + lLineBytes32 * i + j * 4 + 0) = ColorB;
*(dstImage + lLineBytes32 * i + j * 4 + 1) = ColorG;
*(dstImage + lLineBytes32 * i + j * 4 + 2) = ColorR;
*(dstImage + lLineBytes32 * i + j * 4 + 3) = gray<230?0:gray;
}
}
return true;
}
FTFONTDLL_API void FTFontSDK_SetColor(unsigned char R, unsigned char G, unsigned char B);
FTFONTDLL_API void FTFontSDK_SetColor(unsigned char R, unsigned char G, unsigned char B)
{
ColorR = R;
ColorG = G;
ColorB = B;
}
FTFONTDLL_API bool FTFontSDK_Release();
FTFONTDLL_API bool FTFontSDK_Release()
{
FT_Done_Face(face);
FT_Done_FreeType(library);
return true;
}
好了,简单的FTFont.dll就这么完成了,现在我们把它编译一下,好的,我们现在再次编译成功了,恭喜你,已经完成了对Freetype的二次封装
C# Winform|WPF调用
1、我们将FTFont.dll 放到我们可执行文件的同级目录下
2、我们编写调用FTFont.dll的 静态类文件
可以看到,每个函数都和FTFont.dll的导出函数一致
public class FTFontdll
{
[DllImport("kernel32.dll", EntryPoint = "RtlCopyMemory", CharSet = CharSet.Ansi)]
public extern static long CopyMemory(IntPtr dest, IntPtr source, int size);[DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory", CharSet = CharSet.Ansi)]
public extern static long MoveMemory(IntPtr dest, IntPtr source, int size);
/// <summary>
/// 创建句柄
/// </summary>
/// <returns></returns>
[DllImport("FTFontDll.dll", EntryPoint = "FTFontSDK_Init", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public extern static IntPtr FTFontSDK_Init(string ttfPath,uint fontSize);/// <summary>
/// 创建句柄
/// </summary>
/// <returns></returns>
[DllImport("FTFontDll.dll", EntryPoint = "FTFontSDK_SetFlip", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public extern static IntPtr FTFontSDK_SetFlip(uint width,uint height);/// <summary>
/// 创建句柄
/// </summary>
/// <returns></returns>
[DllImport("FTFontDll.dll", EntryPoint = "FTFontSDK_SetColor", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public extern static IntPtr FTFontSDK_SetColor([MarshalAs(UnmanagedType.I1)] byte ColorR, [MarshalAs(UnmanagedType.I1)] byte ColorG, [MarshalAs(UnmanagedType.I1)] byte ColorB);
/// <summary>
/// 创建句柄
/// </summary>
/// <returns></returns>
[DllImport("FTFontDll.dll", EntryPoint = "FTFontSDK_Get", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public extern static IntPtr FTFontSDK_Get(string chStr,IntPtr FontStruct);
/// <summary>
/// 创建句柄
/// </summary>
/// <returns></returns>
[DllImport("FTFontDll.dll", EntryPoint = "FTFontSDK_Release", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public extern static IntPtr FTFontSDK_Release();
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct FontStruct
{
public uint width;
public uint height;
public uint xMin;
public uint xMax;
public uint yMin;
public uint yMax;
public uint bearingY;
public uint bearingX;
public uint origin;
public uint advance;
public IntPtr bitmapBuffer;
};
3、开始调用
public MainWindow()
{
InitializeComponent();FTFontdll.FTFontSDK_Init(@"D:\Code\Project\CT\用户交互软件\IPS\lib\DRScrollFrameRender\DRDengl.ttf",96);
FTFontdll.FTFontSDK_SetColor(Colors.Red.R, Colors.Red.G, Colors.Red.B);
FTFontdll.FTFontSDK_SetFlip(0, 24);
Task.Run(() => {
for (int i = 0; i < 30; i++)
{
IntPtr fontStructPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(FontStruct)));
FTFontdll.FTFontSDK_Get($@"北京时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}", fontStructPtr);
FontStruct fontStruct = (FontStruct)Marshal.PtrToStructure(fontStructPtr, typeof(FontStruct));
Application.Current.Dispatcher.BeginInvoke(new Action(() => {
WriteableBitmap writeableBitmap = new WriteableBitmap((int)fontStruct.width, (int)fontStruct.height, 96, 96, PixelFormats.Bgra32, null);
writeableBitmap.WritePixels(new Int32Rect(0, 0, (int)writeableBitmap.Width, (int)writeableBitmap.Height), fontStruct.bitmapBuffer, (int)writeableBitmap.Width * (int)writeableBitmap.Height * 4, (int)writeableBitmap.BackBufferStride);
fontImage.Source = writeableBitmap;
}));
Marshal.FreeHGlobal(fontStructPtr);
Thread.Sleep(1000);
}
});
}
以下是成品演示
本次实例源代码
gitee: LonelyBoar/Freetype
感谢支持!