先上效果图:
功能介绍:输入文字使用GraphicsPath绘制出文字,然后获取绘制的文字坐标,再根据文字坐标动态绘制文字。
一.画板上先绘制出文字,相关代码如下:
private GraphicsPath textPath = new GraphicsPath();
private Font textFont = new Font("Arial", 164, FontStyle.Regular);
private RectangleF textRect;
public void CreateTextPath(string value)
{
textPath.Reset();
if (!string.IsNullOrWhiteSpace(value))
{
var sf = new StringFormat
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center
};
var size = TextRenderer.MeasureText(value, textFont);
var textPosition = new PointF(this.Width / 2.0f - size.Width / 2.0f,this.Height / 2.0f - size.Height / 2.0f);
textRect = new RectangleF(textPosition, size);
textPath.AddString(value, textFont.FontFamily, (int)textFont.Style, textFont.Size, textRect, sf);
}
this.Invalidate();//重绘
}
二.获取画板上绘制文字的坐标,这里就从GraphicsPath类中取出文字的点坐标(PathPoints)和点类型(PathTypes)。PathTypes是个枚举类型,简单点来说就是一个点坐标对应一个点类型,类型如下所示:
//指定 System.Drawing.Drawing2D.GraphicsPath 对象中点的类型。
public enum PathPointType
{
// 起始点。
Start = 0,
// 连线线段。
Line = 1,
// 立体贝塞尔曲线。
Bezier3 = 3,
// 默认贝塞尔曲线。
Bezier = 3,
//遮盖点。
PathTypeMask = 7,
// 对应线段为虚线。
DashMode = 16,
// 路径标记。
PathMarker = 32,
// 子路径的终结点。
CloseSubpath = 128,
}
private List<PointF> textPoints = new List<PointF>();
private List<byte> textPathTypes = new List<byte>();
public void ParserPathPoint()
{
textPoints.Clear();
textPathTypes.Clear();
using (var m= new Matrix())
textPath.Flatten(m, 0.1f);//将曲线段转换为线段,点坐标类型就会只有线段类型
textPoints.AddRange(textPath.PathPoints);
textPathTypes.AddRange(textPath.PathTypes);
}
三.根据点坐标类型和点坐标绘制出文字,这里需要判断每个点的坐标类型,相关代码如下:
for (int i = 0; i < textPoints.Count; i++){
var pathPointType = (PathPointType)(textPathTypes[i] & (byte)PathPointType.PathTypeMask);
if(pathPointType == PathPointType.Start)//如果是起点,则跳过
{
continue;
}
else
{
PointF currentPoint = textPoints[i];
if (pathPointType == PathPointType.Line) // 绘制线段
{
PointF prevPoint = textPoints[i - 1];
g.DrawLine(Pens.Red, prevPoint, currentPoint);
}
else if (pathPointType == PathPointType.Bezier)
{
PointF prevPoint = textPoints[i - 1];
g.DrawBezier(Pens.Red, prevPoint, textPoints[i], textPoints[i + 1], textPoints[i + 2]);
i += 2;
}
bool isClosed = ((textPathTypes[i] & (byte)PathPointType.CloseSubpath) == (byte)PathPointType.CloseSubpath);
if (isClosed) //检查当前点是否是路径的终点
{
var pathMarker = (PathPointType)(textPathTypes[i] & (byte)PathPointType.PathMarker);
if (pathMarker == PathPointType.PathMarker)
{
continue;
}
if (pathPointType == PathPointType.Bezier)
{
var index = GetStartPointIndex(i - 1);
CalculateControlPoints(textPoints[i], textPoints[index],out PointF point1,out PointF point2);
g.DrawBezier(Pens.Red,textPoints[i], point1,point2, textPoints[index]);
}
else
{
g.DrawLine(Pens.Red, currentPoint, textPoints[GetStartPointIndex(i-1)]);
}
}
}
}
#region CalculateControlPoints
private void CalculateControlPoints(PointF start, PointF end, out PointF cp1, out PointF cp2)
{
// 计算起点到终点的方向向量
float dx = end.X - start.X;
float dy = end.Y - start.Y;
// 控制点距离(可以根据需要调整)
float distance = Math.Min(Math.Abs(dx), Math.Abs(dy)) * 0.5f;
// 控制点1:起点方向
cp1 = new PointF(start.X + dx * 0.25f, start.Y + dy * 0.25f);
// 控制点2:终点方向
cp2 = new PointF(end.X - dx * 0.25f, end.Y - dy * 0.25f);
}
#endregion
1.点数组的第一个点的类型都为PathPointType.Start,如果是PathPointType.Line或PathPointType.Bezier也会被改为PathPointType.Start。
2.PathPointType.Bezier:一段Bezier曲线有4个点,多段Bezier曲线需要3N+1个点。
3.点坐标有131这样的值,它是PathPointType.Bezier与PathPointType.CloseSubpath的和,表示该点是Bezier曲线而且还是子路径的结束点。
4.PathPointType.PathMarker:代码中可以看出它是与前三个点坐标类型一起使用,进行"&"操作后得到点的类型。
关于动态绘制文字,可以使用定时器控制每秒绘制多少个坐标点,就可以实现以上效果。
写在最后:
GDI+学习者,文笔不好,请勿喷!