Sevencat.GlyphRaster
0.1.5
dotnet add package Sevencat.GlyphRaster --version 0.1.5
NuGet\Install-Package Sevencat.GlyphRaster -Version 0.1.5
<PackageReference Include="Sevencat.GlyphRaster" Version="0.1.5" />
<PackageVersion Include="Sevencat.GlyphRaster" Version="0.1.5" />
<PackageReference Include="Sevencat.GlyphRaster" />
paket add Sevencat.GlyphRaster --version 0.1.5
#r "nuget: Sevencat.GlyphRaster, 0.1.5"
#:package Sevencat.GlyphRaster@0.1.5
#addin nuget:?package=Sevencat.GlyphRaster&version=0.1.5
#tool nuget:?package=Sevencat.GlyphRaster&version=0.1.5
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,Y2QuadraticTo: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、LineCountPositionedGlyph:Mask(GlyphMask)+X、YGlyphMask: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. 坑位清单
TextFont.LoadBuiltIn()每次返回新实例 → 用TextFont.BuiltInFlatten之前有二次贝塞尔,段终点永远看X2,Y2;LineTo的X1,Y1是 0(x, y)是左上角,不是基线;layout.Height是整块高度(含行高),可直接当文本盒bounds的X/Y不是 0,平移要减bounds.X/Y,否则文字会跑偏DrawRgb24是 R,G,B 顺序,往 BGR 图上画彩色要换序stride用Mat.Step(),不要用width * 3硬算TextFont没有DisposeCv2.ContourArea判断不了绕向,要自己算有向面积- 轨迹路线挖孔靠回填背景色,只适用于纯色底
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 | Versions 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. |
-
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 |