一、简单聊两句
路径在GDI里面是一个比较特殊的东西,它可以加入很多图形,组成一个路径,用处也是很大的,做复杂图形的组合就比较方便了。就因为方便,我们常常忽略了一个问题,它里面的数据到底是什么样的啊?都是线段吗?我经过尝试,发现并不完全是。
二、路径数据的刨析
1.路径数据的分析
为了涵盖足够复杂的图形,我们在路径中加入文字来做分析。
最简单的一个文字路径的代码:
GraphicsPath Strpath = new GraphicsPath();
Strpath.AddString("U", font.FontFamily, (int)FontStyle.Regular, (float)font.Size, new PointF(300.0f, 50.0f), format);
路径中加入了一个字母。
我们再看看这个路径里的属性PathPoints,里面是一堆点。起初我也没关注过这堆点是啥,因为我一调用DrawPath他就能自己绘制出来,因此我猜测,他就是一堆路径上的点吧。后来,我要把这个文字绘制到别的地方,比如3D里面,可是那里可没有DrawPath调用啊,我只能通过它这堆点去绘制了,因此我把它当作一堆直线段上的点去绘制,发现偏差有点大。
如图:
我用黄色绘制的路径,用蓝色将路径中的点按线段绘制,发现有些地方并不重合。
于是我去参考下官方文档关于路径点的介绍:
GraphicsPath.PathTypes 属性 (System.Drawing.Drawing2D) | Microsoft Learn
原来这些点并不是都一样的!它们有各种的类别标记,也就是路径的PathTypes属性。因而对路径点的解析要从这里下手。
通过调试发现,路径的起始点Type都是0,每段闭合线的终止点Type基本都大于80,然后1表示直线上的点,3表示贝塞尔曲线的控制点。目前还没遇到过其他的类型,至少我在文字里面没看到过别的。因此,路径的点并不完全都是直线段,所以我直接绘制成直线段产生偏差得到了解释。
2.路径数据的线段化
有了上述的实践结果,我们可以对路径点进行整理转化,生成我们需要的全部为线段上的点。
根据点类型分析:
类型为0,起点,无法看出是直线点还是贝塞尔曲线的,因此起点只能作为分段的一个信号。
类型为1和3就不用说,可以进行相应的处理。
类型大于80,可定性为终止点,需要与起点形成闭合。
对于直线上的点,我们不需要处理,直接接收,对于贝塞尔曲线上的点,我们可以参考公式计算:
由于是4个点控制,所以这里是3阶贝塞尔曲线:
由此,可以开始写代码了:
List<List<PointF>> points = new List<List<PointF>>();
PointF before = new PointF();
for (int i = 0; i < Strpath.PathPoints.Length;)
{
if (Strpath.PathTypes[i] == 0)
points.Add(new List<PointF>());
if (Strpath.PathTypes[i] == 3)
{
int segments = 10;
for (int j = 1; j < segments + 1; j++)
{
double t = (double)j / (double)segments;
PointF point = new PointF();
point.X = (1.0f - t) * (1.0f - t) * (1.0f - t) * before.X + 3 * t * (1.0f - t) * (1.0f - t) * Strpath.PathPoints[i].X + 3 * t * t * (1.0f - t) * Strpath.PathPoints[i + 1].X + t * t * t * Strpath.PathPoints[i + 2].X;
point.Y = (1.0f - t) * (1.0f - t) * (1.0f - t) * before.Y + 3 * t * (1.0f - t) * (1.0f - t) * Strpath.PathPoints[i].Y + 3 * t * t * (1.0f - t) * Strpath.PathPoints[i + 1].Y + t * t * t * Strpath.PathPoints[i + 2].Y;
points[points.Count - 1].Add(point);
}
if (Strpath.PathTypes[i + 2] > 80)
{
PointF last = new PointF(points[points.Count - 1][0]);
points[points.Count - 1].Add(last);
}
i += 3;
}
else
{
points[points.Count - 1].Add(Strpath.PathPoints[i]);
if (Strpath.PathTypes[i] > 80)
{
PointF last = new PointF(points[points.Count - 1][0]);
points[points.Count - 1].Add(last);
}
i++;
}
before = points[points.Count - 1][points[points.Count - 1].Count - 1];
}
经过解析就可以得到线段化后的点集,有了这个点集,我们可以在GDI里面绘制,也可以在OpenGL里绘制,还可以对这些点做各种变换。
以下是我绘制在OpenGL里的一个效果图:
三、结尾
对路径的探索就到这里了,很多东西,深入探索下才能看到更多。