Sevencat.GlyphRaster 0.1.5

dotnet add package Sevencat.GlyphRaster --version 0.1.5
                    
NuGet\Install-Package Sevencat.GlyphRaster -Version 0.1.5
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Sevencat.GlyphRaster" Version="0.1.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Sevencat.GlyphRaster" Version="0.1.5" />
                    
Directory.Packages.props
<PackageReference Include="Sevencat.GlyphRaster" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Sevencat.GlyphRaster --version 0.1.5
                    
#r "nuget: Sevencat.GlyphRaster, 0.1.5"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Sevencat.GlyphRaster@0.1.5
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Sevencat.GlyphRaster&version=0.1.5
                    
Install as a Cake Addin
#tool nuget:?package=Sevencat.GlyphRaster&version=0.1.5
                    
Install as a Cake Tool

GlyphRaster 调用说明

GlyphRaster 是一个不依赖 GDI+/FreeType/WPF 的纯托管 TrueType 光栅化库:自己解析字体表、自己算排版、自己往像素里铺字形,也能只把字形轮廓(矢量路径)导出给别人画。

  • 程序集:F:\github\algotoolkit\GlyphRaster.dll(GlyphRaster 0.1.5.0)
  • 命名空间:GlyphRaster
  • 内置字体:文泉驿微米黑(覆盖中日韩),编译进 DLL,不依赖系统里装了什么字体

本文所有 API 签名、坐标语义、像素顺序、绕向规则都是在 0.1.5 上实测确认过的,不是照文档抄的。

1. 引用

项目里是按 dll 直接引用的:

<ItemGroup>
  <Reference Include="GlyphRaster">
    <HintPath>..\GlyphRaster.dll</HintPath>
  </Reference>
</ItemGroup>

2. 最小用法

using GlyphRaster;

var font = TextFont.BuiltIn;                 // 内置字体,全局共享实例,别用 LoadBuiltIn()
var layout = font.Layout("测试文字 Ab 0.98", 16f);   // 排版,得到字形位置与整体宽高
Console.WriteLine($"{layout.Width} x {layout.Height}");   // 122 x 19

layout 里已经包含每个字的光栅位图(PositionedGlyph.Mask),直接交给 TextPainter 就能画:

TextPainter.DrawRgb24(pixels, width, height, stride, layout, x, y, RgbColor.White);

3. 画到 OpenCV 的 Mat 上(路线 A:字形位图)

TextPainter 只认 Span<byte>,所以给 Mat 的内存包一层视图即可。注意 stride 必须用 Mat.Step(),OpenCV 的行间可能有对齐填充。

public static void DrawText(Mat bgr24, string text, int x, int y, float fontSize = 16)
{
    if (bgr24.Type() != MatType.CV_8UC3)
        throw new ArgumentException("需要 CV_8UC3");

    var font = TextFont.BuiltIn;
    var layout = font.Layout(text, fontSize);

    unsafe
    {
        var stride = (int)bgr24.Step();
        var pixels = new Span<byte>((void*)bgr24.Data, stride * bgr24.Height);
        TextPainter.DrawRgb24(pixels, bgr24.Width, bgr24.Height, stride, layout, x, y, RgbColor.White);
    }
}

要点:

事项 实测结论
(x, y) 含义 文本块左上角,不是基线
超出画布 库自己裁剪,不会越界写内存
与 Cv2.Rectangle 共存 写的是同一块内存,先画底色再铺字即可
彩色文字 DrawRgb24 按 R,G,B 顺序写内存,画到 BGR24 的 Mat 上要交换 R/B;白色三通道相同,无影响
BGRA 位图 用 DrawBgra32

4. 只取轮廓、自己画(路线 B:轨迹 + FillPoly)

GetTextPaths 返回整段文字的矢量轮廓,Flatten 把二次贝塞尔离散成折线。

var font = TextFont.BuiltIn;
var paths = font.GetTextPaths("回口A0", 16f).Flatten(0.25f);   // 0.25 = 离散容差(像素)

// paths.Bounds 是字形墨迹的包围盒,注意它不是从 (0,0) 开始
var bounds = paths.Bounds;
var offsetX = targetX - bounds.X;
var offsetY = targetY - bounds.Y;

foreach (var path in paths)                    // VectorPath: Start + Segments
{
    var pts = new List<Point>
    {
        new((int)Math.Round(path.Start.X + offsetX), (int)Math.Round(path.Start.Y + offsetY)),
    };

    foreach (var seg in path.Segments)          // Flatten 之后只剩 LineTo
    {
        if (seg.Verb is PathVerb.LineTo or PathVerb.QuadraticTo)
            pts.Add(new Point((int)Math.Round(seg.X2 + offsetX), (int)Math.Round(seg.Y2 + offsetY)));
    }

    if (pts.Count >= 3)
        Cv2.FillPoly(mat, new[] { pts.ToArray() }, Scalar.White, LineTypes.AntiAlias);
}

4.1 段的坐标要取 X2,Y2

PathSegment 是 Verb + (X1,Y1) + (X2,Y2):

  • LineTo:X1,Y1 恒为 0,终点在 X2,Y2
  • QuadraticTo:X1,Y1 是控制点,X2,Y2 是终点
  • Close / MoveTo:填充时要跳过

取 X1,Y1 当终点,字形会整片塌掉——这是最容易踩的坑。

4.2 孔洞要按绕向挖

Cv2.FillPoly 不管绕向,一律填实,所以 回、口 的孔会被糊死。而 Cv2.ContourArea 返回绝对值,判断不了方向,得自己算有向面积(鞋带公式):

static double Shoelace(IReadOnlyList<Point> pts)
{
    double sum = 0;
    for (var i = 0; i < pts.Count; i++)
    {
        var a = pts[i];
        var b = pts[(i + 1) % pts.Count];
        sum += (double)a.X * b.Y - (double)b.X * a.Y;
    }
    return sum / 2.0;                      // >0 外轮廓,<0 孔洞(屏幕坐标 y 向下)
}

实测:口 外轮廓 +130.5、内孔 -78.63;回 是 +158 / -100 / +36 / -12.7,正负交替,符合 TrueType 规则。

挖孔的做法是用背景色把负面积再填一遍,所以背景必须是已知纯色(比如标签底那块实心矩形)。如果底色是图片,这条路线就要先建掩码做混合,不如直接用路线 A。

实测效果:同一段 回口A0,不回填孔洞时白色像素 454,回填后 86。

5. 主要 API 一览

TextFont

成员 说明
TextFont.BuiltIn 内置字体共享实例,首次访问解压解析,之后复用,内部字形缓存带锁,可跨线程共享
TextFont.LoadBuiltIn(BuiltInFont font = WenQuanYiMicroHei) 每次调用返回新实例(实测 ReferenceEquals(BuiltIn, LoadBuiltIn()) = False),字形缓存挂在新实例上,逐帧调用等于逐帧重新光栅化,别这么用
Load(byte[]) / LoadFromFile(string) / LoadFromStream(Stream) 用自己的 ttf
Layout(string text, float size, TextLayoutOptions options = null) 排版,支持 MaxWidth(自动折行)、LineHeight
Measure(string, float) 只量宽度
GetTextPaths(string text, float size, TextLayoutOptions options = null) 整段文字的轮廓集合
GetGlyphPath / GetGlyphPaths(int codePoint, float size) 单字轮廓
GetGlyphMask(int codePoint, float size) 单字位图
GetAscent / GetDescent / GetLineHeight(float size) 字体度量
HasGlyph(int codePoint) 是否有该字符
FamilyName / UnitsPerEm 字体信息

TextFont 不实现 IDisposable,不能 using。

TextLayout / PositionedGlyph / GlyphMask

  • TextLayout:Glyphs、Width、Height、Ascent、LineHeight、LineCount
  • PositionedGlyph:Mask(GlyphMask)+ X、Y
  • GlyphMask:Width/Height/OffsetX/OffsetY/Coverage/Advance/IsEmpty

PathCollection / VectorPath / PathSegment

成员 说明
PathCollection.Count / Bounds / IsEmpty 轮廓条数、整体包围盒
Flatten(float tolerance = 0.25f) 曲线离散成折线
Transform(Matrix3x2) 平移缩放旋转(也能直接算成目标坐标,省得自己加 offset)
AsClosedPath() 补齐闭合
ToSvgPathData() 导出 SVG path 字符串,调试很方便
VectorPath.Start / Segments / IsClosed / Bounds 单条轮廓
PathSegment.Verb MoveTo / LineTo / QuadraticTo / Close

TextPainter

void DrawRgb24 (Span<byte> pixels, int width, int height, int stride, TextLayout layout, int x, int y, RgbColor color)
void DrawBgra32(Span<byte> pixels, int width, int height, int stride, TextLayout layout, int x, int y, RgbColor color)

带覆盖率混合(抗锯齿),只写目标像素,不做任何字体接口调用。

EmbeddedFonts / RgbColor

  • EmbeddedFonts.Count / GetFontData() / GetFontSize() / CreateFont():取内置字体原始数据
  • RgbColor:new RgbColor(r, g, b)、RgbColor.White、RgbColor.Black

6. 性能(实测)

条件:50 个 OCR 标签,1280×720 CV_8UC3,字号 16,预热 5 帧后取 50 帧平均,同进程两轮。

绘制方式 每帧耗时 说明
Cv2.PutText + 缓存 FontFace 1.76 / 2.49 ms 文字本身最省,但依赖系统 simhei.ttf
字形位图 DrawRgb24 2.58 / 2.99 ms 内置字体,不依赖系统
字形轨迹 GetTextPaths + FillPoly 13.4 / 16.8 ms 慢一个数量级

同一份代码,若每帧重建字体(上一版写法):

绘制方式 改前 改后
Cv2.PutText(每帧 new FontFace) 13.2 ~ 14.0 ms 1.8 ~ 2.5 ms
字形位图(每帧 LoadBuiltIn()) 6.6 ~ 8.3 ms 2.6 ~ 3.0 ms
字形轨迹 12.9 ~ 14.8 ms 13.4 ~ 16.8 ms(本就不受影响)

拆解(每帧 50 标签):

框 + 标签底(Cv2.Rectangle)   1.41 ms   ← 固定成本,两条路线都要付
font.Layout x50                0.05 ms
font.GetTextPaths x50          5.44 ms   ← 轨迹路线的主要开销
+ Flatten                      7.73 ms
TextPainter.DrawRgb24 x50      约 1.2 ms

结论:

  • 只求快 → 位图路线;文字部分约 0.4~1.2 ms,剩下的时间基本都花在 Cv2.Rectangle 的抗锯齿边框上
  • 需要轮廓(描边、动画、几何运算)→ 轨迹路线,代价是 GetTextPaths 的固有开销,除非自己缓存「文本 → 轮廓」的结果
  • 任何情况下都不要每帧重建字体实例;new FontFace 解析 simhei.ttf 单次就要 8~10 ms

7. 坑位清单

  1. TextFont.LoadBuiltIn() 每次返回新实例 → 用 TextFont.BuiltIn
  2. Flatten 之前有二次贝塞尔,段终点永远看 X2,Y2;LineTo 的 X1,Y1 是 0
  3. (x, y) 是左上角,不是基线;layout.Height 是整块高度(含行高),可直接当文本盒
  4. bounds 的 X/Y 不是 0,平移要减 bounds.X/Y,否则文字会跑偏
  5. DrawRgb24 是 R,G,B 顺序,往 BGR 图上画彩色要换序
  6. stride 用 Mat.Step(),不要用 width * 3 硬算
  7. TextFont 没有 Dispose
  8. Cv2.ContourArea 判断不了绕向,要自己算有向面积
  9. 轨迹路线挖孔靠回填背景色,只适用于纯色底

8. 本仓库里的封装

util/GlyphRasterTextUtil.cs:

方法 路线
DrawOcrResultBgr24ByGlyph(this Mat, List<OcrResultItem>, float fontSize = 16) 位图(TextPainter.DrawRgb24)
DrawOcrResultBgr24ByGlyphPath(this Mat, List<OcrResultItem>, float fontSize = 16) 轨迹(GetTextPaths + Cv2.FillPoly)

两者的输出布局一致:红框 → 框上方红色标签底 → 白字。panel/ocr/OcrPanel 的下拉框可切换「字形位图 / 字形轨迹 / PutText(旧)」三种方式。

老的 util/SdcbOcrUtil.DrawOcrResultBgr24(Cv2.PutText)保留可用,内部把 FontFace 缓存成了单例;系统没装黑体时仍会抛 FileNotFoundException(失败不入缓存,放好字体文件后不重启也能恢复)。

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.5 0 11/7/2026