封装C# 可以调用的FreeType.dll 用于生成bitmap

        最近公司需要在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

感谢支持!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值