SkiaSharp 中的路径基础知识

Download Sample下载示例

探索用于合并连接的直线和曲线的 SkiaSharp SKPath 对象

图形路径最重要的功能之一是能够定义何时应连接多条线以及何时不应连接多条线。 正如以下两个三角形的顶部所示,差异可能很大:

Two triangles showing the difference between connected and disconnected lines

图形路径由 SKPath 对象封装。 路径是一个或多个轮廓的集合。 每个轮廓都是连接的直线和曲线的集合。 轮廓间相互不连接,但它们在视觉上可能会重叠。 有时,单个轮廓可以重叠其本身。

轮廓线通常从调用 SKPath 的以下方法开始:

  • MoveTo,用于开始新的轮廓线

该方法的参数是一个点,你可以将其表示为 SKPoint 值或单独的 X 和 Y 坐标。 调用 MoveTo 会在轮廓线的起点处建立一个点和一个初始当前点。 可以调用以下方法,用直线或曲线继续绘制轮廓线,从当前点绘制到方法中指定的点,然后该点将成为新的当前点:

  • LineTo,用于向路径添加一条直线
  • ArcTo,用于添加圆弧,这是圆或椭圆圆周上的线
  • CubicTo,用于添加三次贝塞尔曲线
  • QuadTo,用于添加二次贝塞尔曲线
  • ConicTo,用于添加合理的二次贝塞尔曲线,可以准确渲染圆锥曲线(椭圆、抛物线和双曲线)

这五种方法都不包含描述直线或曲线所需的所有信息。 这五种方法中的每一种都可与紧邻其前面的方法调用所建立的当前点结合使用。 例如,LineTo 方法基于当前点向轮廓线添加一条直线,因此 LineTo 的参数只是单个点。

SKPath 类还定义了与这六种方法同名的方法,但都以 R 开头:

R 代表相对。 这些方法与不带 R 的相应方法的语法相同,但相对于当前点。 可使用这些方法在多次调用的方法中轻松绘制路径的相似部分。

轮廓线以对 MoveToRMoveTo 的另一次调用结束,这一次调用会开始新的轮廓线,也可调用 Close 来闭合轮廓线。 Close 方法会自动附加一条直线,从当前点连接到轮廓线的第一个点,并将路径标记为闭合,这意味着它将在没有任何描边帽的情况下进行渲染。

有关非闭合轮廓线和闭合轮廓线之间的差异,请参阅“两个三角形轮廓线”页面中的说明,该页面使用具有两条轮廓线的 SKPath 对象来渲染两个三角形。 第一条轮廓线是非闭合曲线,第二条轮廓线是闭合曲线。 以下是 TwoTriangleContoursPage 类:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    // Create the path
    SKPath path = new SKPath();

    // Define the first contour
    path.MoveTo(0.5f * info.Width, 0.1f * info.Height);
    path.LineTo(0.2f * info.Width, 0.4f * info.Height);
    path.LineTo(0.8f * info.Width, 0.4f * info.Height);
    path.LineTo(0.5f * info.Width, 0.1f * info.Height);

    // Define the second contour
    path.MoveTo(0.5f * info.Width, 0.6f * info.Height);
    path.LineTo(0.2f * info.Width, 0.9f * info.Height);
    path.LineTo(0.8f * info.Width, 0.9f * info.Height);
    path.Close();

    // Create two SKPaint objects
    SKPaint strokePaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Magenta,
        StrokeWidth = 50
    };

    SKPaint fillPaint = new SKPaint
    {
        Style = SKPaintStyle.Fill,
        Color = SKColors.Cyan
    };

    // Fill and stroke the path
    canvas.DrawPath(path, fillPaint);
    canvas.DrawPath(path, strokePaint);
}

第一条轮廓线包含对使用 X 和 Y 坐标而不是 SKPoint 值的 MoveTo 的调用,后跟三个对 LineTo 的调用以绘制三角形的三条边。 第二条轮廓线只包含两个对 LineTo 的调用,但它通过对 Close 的调用(用于闭合轮廓线)来完成轮廓线。 这个差别是很显著的:

Triple screenshot of the Two Triangle Contours page

如你所见,第一条轮廓线显然是一系列三条相连的线,但终点与起点并不相连。 两条线在顶部重叠。 第二条轮廓线显然是闭合的,并且对 LineTo 的调用将减少一次,因为 Close 方法会自动添加最后一条线来闭合轮廓线。

SKCanvas 仅定义了一种 DrawPath 方法,在本演示中该方法被调用两次以填充路径并对路径描边。 所有轮廓线都将被填充,即使是那些未闭合的轮廓线也会被填充。 为了填充未闭合的路径,假设轮廓线的起点和终点之间存在一条直线。 如果从第一条轮廓线中删除最后一个 LineTo,或从第二条轮廓线中删除 Close 调用,则每条轮廓线都将只有两条边,但会像三角形一样被填充。

SKPath 定义了许多其他方法和属性。 以下方法会将所有轮廓线添加到路径中,这些轮廓线可能是闭合的,也可能是非闭合的,具体取决于方法:

请记住,SKPath 对象仅定义几何图形,即一系列点和连接线。 只有当 SKPathSKPaint 对象合并时,路径才会以特定的颜色、描边宽度等进行渲染。 另外,请记住,传递给 DrawPath 方法的 SKPaint 对象可以确定整个路径的特征。 如果要绘制需要多种颜色的内容,则必须为每种颜色使用单独的路径。

正如一条线的起点和终点的外观由描边帽定义一样,两条线之间的连接线的外观由描边连接定义。 可通过将 SKPaintStrokeJoin 属性设置为 SKStrokeJoin 枚举的成员来指定这一点:

  • Miter 表示尖角连接
  • Round 表示圆角连接
  • Bevel 表示切断的连接

“描边连接”页面介绍了这三条描边连接,其代码类似于“描边帽”页面。 这是 StrokeJoinsPage 类中的 PaintSurface 事件处理程序:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    SKPaint textPaint = new SKPaint
    {
        Color = SKColors.Black,
        TextSize = 75,
        TextAlign = SKTextAlign.Right
    };

    SKPaint thickLinePaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Orange,
        StrokeWidth = 50
    };

    SKPaint thinLinePaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Black,
        StrokeWidth = 2
    };

    float xText = info.Width - 100;
    float xLine1 = 100;
    float xLine2 = info.Width - xLine1;
    float y = 2 * textPaint.FontSpacing;
    string[] strStrokeJoins = { "Miter", "Round", "Bevel" };

    foreach (string strStrokeJoin in strStrokeJoins)
    {
        // Display text
        canvas.DrawText(strStrokeJoin, xText, y, textPaint);

        // Get stroke-join value
        SKStrokeJoin strokeJoin;
        Enum.TryParse(strStrokeJoin, out strokeJoin);

        // Create path
        SKPath path = new SKPath();
        path.MoveTo(xLine1, y - 80);
        path.LineTo(xLine1, y + 80);
        path.LineTo(xLine2, y + 80);

        // Display thick line
        thickLinePaint.StrokeJoin = strokeJoin;
        canvas.DrawPath(path, thickLinePaint);

        // Display thin line
        canvas.DrawPath(path, thinLinePaint);
        y += 3 * textPaint.FontSpacing;
    }
}

下面是正在运行的程序:

Triple screenshot of the Stroke Joins page

斜角连接由线条连接处的尖点组成。 当两条线以小角度连接时,斜角连接可能会变得相当长。 为了防止斜角连接过长,斜角连接的长度受限于 SKPaintStrokeMiter 属性值。 超过此长度的斜角连接将被切掉而变为棱台连接。