贝塞尔曲线的三种类型

Download Sample下载示例

了解如何使用 SkiaSharp 呈现三次、二次和圆锥贝塞尔曲线

贝塞尔曲线以皮埃尔·贝塞尔(1910-1999年)命名,他是雷诺汽车公司的法国工程师,他利用曲线设计汽车车身。

贝塞尔曲线以适合交互式设计而闻名:它们表现良好,换句话说,没有奇异之处导致曲线变得无限或不笨拙,而且它们通常具有美观性:

A sample Bezier curve

基于计算机的字体的字符轮廓通常用贝塞尔曲线定义。

关于贝塞尔曲线的维基百科文章包含一些有用的背景信息。 贝塞尔曲线这一术语实际上指的是一系列类似的曲线。 SkiaSharp 支持三种类型的贝塞尔曲线,称为立方、二次和圆锥。 该圆锥体也称为理性二次

立方贝塞尔曲线

立方是贝塞尔曲线的类型,大多数开发人员想到贝塞尔曲线的主题出现时。

可以使用具有三个 SKPath 参数的方法或具有单独 x 参数和 y 参数的 CubicTo 重载,将立方贝塞尔曲线添加到对象 CubicToSKPoint

public void CubicTo (SKPoint point1, SKPoint point2, SKPoint point3)

public void CubicTo (Single x1, Single y1, Single x2, Single y2, Single x3, Single y3)

曲线从轮廓的当前点开始。 完整的立方贝塞尔曲线由四个点定义:

  • 起点:轮廓中的当前点;如果 MoveTo 尚未调用,则为 (0, 0)
  • 第一个控制点:CubicTo 调用中的 point1
  • 第二个控制点:CubicTo 调用中的 point2
  • 终结点:CubicTo 调用中的 point3

生成的曲线从起点开始,终点处结束。 曲线通常不会通过两个控制点:相反,控制点的功能非常像磁铁,以拉曲线指向它们。

获得立方贝塞尔曲线感觉的最佳方式是试验。 这是派生自 InteractivePage贝塞尔曲线页的用途。 BezierCurvePage.xaml 文件实例化 SKCanvasViewTouchEffectBezierCurvePage.xaml.cs 代码隐藏文件在其构造函数中创建四个 TouchPoint 对象。 PaintSurface 事件处理程序基于四个 TouchPoint 对象创建一个 SKPath 呈现贝塞尔曲线,并绘制从控制点到终点的虚线切线:

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

    canvas.Clear();

    // Draw path with cubic Bezier curve
    using (SKPath path = new SKPath())
    {
        path.MoveTo(touchPoints[0].Center);
        path.CubicTo(touchPoints[1].Center,
                     touchPoints[2].Center,
                     touchPoints[3].Center);

        canvas.DrawPath(path, strokePaint);
    }

    // Draw tangent lines
    canvas.DrawLine(touchPoints[0].Center.X,
                    touchPoints[0].Center.Y,
                    touchPoints[1].Center.X,
                    touchPoints[1].Center.Y, dottedStrokePaint);

    canvas.DrawLine(touchPoints[2].Center.X,
                    touchPoints[2].Center.Y,
                    touchPoints[3].Center.X,
                    touchPoints[3].Center.Y, dottedStrokePaint);

    foreach (TouchPoint touchPoint in touchPoints)
    {
       touchPoint.Paint(canvas);
    }
}

此处它正在运行:

Triple screenshot of the Bezier Curve page

从数学上看,曲线是立方多项式。 曲线在最多三点处相交直线。 在起点处,曲线始终与从起点到第一个控制点的直线相同,并且方向相同。 在终点,曲线始终与从第二个控制点到终点的直线相同,并且方向相同。

立方贝塞尔曲线始终由连接四点的凸四边线绑定。 这称为凸壳。 如果控制点位于起点和终点之间的直线上,则贝塞尔曲线呈现为直线。 但曲线也可以交叉自身,如第三个屏幕截图所示。

路径轮廓可以包含多个连接的立方贝塞尔曲线,但两个立方贝塞尔曲线之间的连接将平滑,前提是以下三个点是粗糙的(即躺在直线上):

  • 第一条曲线的第二个控制点
  • 第一条曲线的终点,这也是第二条曲线的起点
  • 第二条曲线的第一个控制点

在下一篇有关 SVG 路径数据的文章中,你将发现一个工具,以简化平滑连接的贝塞尔曲线的定义。

有时,了解呈现立方贝塞尔曲线的基础参数公式会很有用。 对于介于 0 到 1 的 t 范围,参数公式如下所示:

x(t) = (1 – t)³x₀ + 3t(1 – t)²x₁ + 3t²(1 – t)x₂ + t³x₃

y(t) = (1 – t)³y₀ + 3t(1 – t)²y₁ + 3t²(1 – t)y₂ + t³y₃

最高指数 3 确认这些是立方多项式。 很容易验证当等于 0 时 t ,点为 (x₀, y₀),即起点,当等于 1 时 t ,该点为 (x₃, y₃),即终点。 接近起点(对于低值 t),第一个控制点 (x₁, y₁) 具有强效果,接近终点('t'的高值)第二个控制点 (x₂, y₂) 具有很强的效果。

贝塞尔曲线近似于圆弧

有时使用贝塞尔曲线呈现圆弧是方便的。立方贝塞尔曲线可以近似于圆弧,最多四分之一圆,因此四条连接的贝塞尔曲线可以定义整个圆。 这一近似在 25 年前发表的两篇文章中进行了讨论:

Tor Dokken等人,“通过曲率-连续贝塞尔曲线对圆圈的良好近似,”计算机辅助几何设计7 (1990 年),33-41。

Michael Goldapp,“由立方多项式的圆弧近似” ,计算机辅助几何设计 8(1991 年),227-238。

下图显示了四个标记 pto的点, pt1pt2以及 pt3 定义一个贝塞尔曲线(以红色显示),该曲线近似于圆弧:

Approximation of a circular arc with a Bézier curve

从起点和终点到控制点的线条与圆和贝塞尔曲线的正切,并且其长度为 L。上面引用的第一篇文章表明,当长度 L 计算时,贝塞尔曲线最接近圆弧:

L = 4 × tan(α / 4) / 3

此图显示了 45 度的角度,因此 L 等于 0.265。 在代码中,该值将乘以圆圈的所需半径。

使用贝塞尔圆形弧线页,可以尝试定义贝塞尔曲线,以近似圆弧,角度范围高达 180 度。 BezierCircularArcPage.xaml 文件实例化 SKCanvasView 和用于选择角度的 SliderPaintSurfaceBezierCircularArgPage.xaml.cs 代码隐藏文件中的事件处理程序 使用转换将点 (0, 0) 设置为画布的中心。 它绘制一个以该点为中心的圆进行比较,然后计算贝塞尔曲线的两个控制点:

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

    canvas.Clear();

    // Translate to center
    canvas.Translate(info.Width / 2, info.Height / 2);

    // Draw the circle
    float radius = Math.Min(info.Width, info.Height) / 3;
    canvas.DrawCircle(0, 0, radius, blackStroke);

    // Get the value of the Slider
    float angle = (float)angleSlider.Value;

    // Calculate length of control point line
    float length = radius * 4 * (float)Math.Tan(Math.PI * angle / 180 / 4) / 3;

    // Calculate sin and cosine for half that angle
    float sin = (float)Math.Sin(Math.PI * angle / 180 / 2);
    float cos = (float)Math.Cos(Math.PI * angle / 180 / 2);

    // Find the end points
    SKPoint point0 = new SKPoint(-radius * sin, radius * cos);
    SKPoint point3 = new SKPoint(radius * sin, radius * cos);

    // Find the control points
    SKPoint point0Normalized = Normalize(point0);
    SKPoint point1 = point0 + new SKPoint(length * point0Normalized.Y,
                                          -length * point0Normalized.X);

    SKPoint point3Normalized = Normalize(point3);
    SKPoint point2 = point3 + new SKPoint(-length * point3Normalized.Y,
                                          length * point3Normalized.X);

    // Draw the points
    canvas.DrawCircle(point0.X, point0.Y, 10, blackFill);
    canvas.DrawCircle(point1.X, point1.Y, 10, blackFill);
    canvas.DrawCircle(point2.X, point2.Y, 10, blackFill);
    canvas.DrawCircle(point3.X, point3.Y, 10, blackFill);

    // Draw the tangent lines
    canvas.DrawLine(point0.X, point0.Y, point1.X, point1.Y, dottedStroke);
    canvas.DrawLine(point3.X, point3.Y, point2.X, point2.Y, dottedStroke);

    // Draw the Bezier curve
    using (SKPath path = new SKPath())
    {
        path.MoveTo(point0);
        path.CubicTo(point1, point2, point3);
        canvas.DrawPath(path, redStroke);
    }
}

// Vector methods
SKPoint Normalize(SKPoint v)
{
    float magnitude = Magnitude(v);
    return new SKPoint(v.X / magnitude, v.Y / magnitude);
}

float Magnitude(SKPoint v)
{
    return (float)Math.Sqrt(v.X * v.X + v.Y * v.Y);
}

起点和终点(point0point3)是根据圆的正态参数公式计算的。 由于圆以 (0, 0) 居中,因此也可以将这些点视为从圆中心到周的径向量。 控制点位于与圆的正切线上,因此它们与这些径向量直角。 向量从右角度到另一个向量只是交换了 X 和 Y 坐标的原始向量,其中一个坐标为负值。

下面是以不同角度运行的程序:

Triple screenshot of the Bezier Circular Arc page

仔细查看第三张屏幕截图,你将看到,当角度为 180 度时,贝塞尔曲线明显偏离半圆形,但 iOS 屏幕显示,当角度为 90 度时,它似乎适合四分之一圆。

当四分之一圆面向如下时,计算两个控制点的坐标非常简单:

Approximation of a quarter circle with a Bézier curve

如果圆的半径为 100,则 L 为 55,这是一个容易记住的数字。

“化圆为方”页对一个圆和一个正方形之间的图形进行动画处理。 圆是四条贝塞尔曲线的近似值,其坐标显示在类中此数组定义 SquaringTheCirclePage 的第一列中:

public class SquaringTheCirclePage : ContentPage
{
    SKPoint[,] points =
    {
        { new SKPoint(   0,  100), new SKPoint(     0,    125), new SKPoint() },
        { new SKPoint(  55,  100), new SKPoint( 62.5f,  62.5f), new SKPoint() },
        { new SKPoint( 100,   55), new SKPoint( 62.5f,  62.5f), new SKPoint() },
        { new SKPoint( 100,    0), new SKPoint(   125,      0), new SKPoint() },
        { new SKPoint( 100,  -55), new SKPoint( 62.5f, -62.5f), new SKPoint() },
        { new SKPoint(  55, -100), new SKPoint( 62.5f, -62.5f), new SKPoint() },
        { new SKPoint(   0, -100), new SKPoint(     0,   -125), new SKPoint() },
        { new SKPoint( -55, -100), new SKPoint(-62.5f, -62.5f), new SKPoint() },
        { new SKPoint(-100,  -55), new SKPoint(-62.5f, -62.5f), new SKPoint() },
        { new SKPoint(-100,    0), new SKPoint(  -125,      0), new SKPoint() },
        { new SKPoint(-100,   55), new SKPoint(-62.5f,  62.5f), new SKPoint() },
        { new SKPoint( -55,  100), new SKPoint(-62.5f,  62.5f), new SKPoint() },
        { new SKPoint(   0,  100), new SKPoint(     0,    125), new SKPoint() }
    };
    ...
}

第二列包含四条贝塞尔曲线的坐标,该曲线定义一个正方形,其面积与圆的面积大致相同。 (绘制一个正方形,与给定圆的确切区域是化圆为方无法解决的经典几何问题。)对于使用贝塞尔曲线呈现正方形,每个曲线的两个控制点相同,它们与起点和终点相等,因此贝塞尔曲线呈现为直线。

数组的第三列用于动画的内插值。 页面将计时器设置为 16 毫秒,PaintSurface 处理程序以该速率调用:

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

    canvas.Clear();

    canvas.Translate(info.Width / 2, info.Height / 2);
    canvas.Scale(Math.Min(info.Width / 300, info.Height / 300));

    // Interpolate
    TimeSpan timeSpan = new TimeSpan(DateTime.Now.Ticks);
    float t = (float)(timeSpan.TotalSeconds % 3 / 3);   // 0 to 1 every 3 seconds
    t = (1 + (float)Math.Sin(2 * Math.PI * t)) / 2;     // 0 to 1 to 0 sinusoidally

    for (int i = 0; i < 13; i++)
    {
        points[i, 2] = new SKPoint(
            (1 - t) * points[i, 0].X + t * points[i, 1].X,
            (1 - t) * points[i, 0].Y + t * points[i, 1].Y);
    }

    // Create the path and draw it
    using (SKPath path = new SKPath())
    {
        path.MoveTo(points[0, 2]);

        for (int i = 1; i < 13; i += 3)
        {
            path.CubicTo(points[i, 2], points[i + 1, 2], points[i + 2, 2]);
        }
        path.Close();

        canvas.DrawPath(path, cyanFill);
        canvas.DrawPath(path, blueStroke);
    }
}

这些点是根据 t 的正弦振荡值进行内插的。 然后,内插点用于构造一系列连接的贝塞尔曲线。 下面是正在运行的动画:

Triple screenshot of the Squaring the Circle page

如果没有算法上灵活的曲线,这种动画是不可能的,这些曲线足够灵活,可以呈现为圆形弧线和直线。

贝塞尔无穷大页还利用贝塞尔曲线近似圆弧的能力。下面是 BezierInfinityPage 类中的 PaintSurface 处理程序:

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

    canvas.Clear();

    using (SKPath path = new SKPath())
    {
        path.MoveTo(0, 0);                                // Center
        path.CubicTo(  50,  -50,   95, -100,  150, -100); // To top of right loop
        path.CubicTo( 205, -100,  250,  -55,  250,    0); // To far right of right loop
        path.CubicTo( 250,   55,  205,  100,  150,  100); // To bottom of right loop
        path.CubicTo(  95,  100,   50,   50,    0,    0); // Back to center  
        path.CubicTo( -50,  -50,  -95, -100, -150, -100); // To top of left loop
        path.CubicTo(-205, -100, -250,  -55, -250,    0); // To far left of left loop
        path.CubicTo(-250,   55, -205,  100, -150,  100); // To bottom of left loop
        path.CubicTo( -95,  100,  -50,   50,    0,    0); // Back to center
        path.Close();

        SKRect pathBounds = path.Bounds;
        canvas.Translate(info.Width / 2, info.Height / 2);
        canvas.Scale(0.9f * Math.Min(info.Width / pathBounds.Width,
                                     info.Height / pathBounds.Height));

        using (SKPaint paint = new SKPaint())
        {
            paint.Style = SKPaintStyle.Stroke;
            paint.Color = SKColors.Blue;
            paint.StrokeWidth = 5;

            canvas.DrawPath(path, paint);
        }
    }
}

在图纸上绘制这些坐标来了解这些坐标的相关方式可能是一个很好的练习。 无穷大符号以点(0, 0) 为中心,两个循环的中心为 (–150, 0) 和 (150, 0),弧度为 100。 在 CubicTo 命令系列中,可以看到控制点的 X 坐标(这些值为 –150 加减 55)、205 和 95(150 加减 55),以及右侧和左侧的 250 和 –250。 唯一的例外是无穷大符号交叉在中心。 在这种情况下,控制点的坐标为 50 和 –50,以缩小中心附近的曲线。

下面是无穷大符号:

Triple screenshot of the Bézier Infinity page

它比 Arc 无穷大页面从“三种方式绘制 Arc”一文呈现的无穷大符号向中心更平滑。

二次贝塞尔曲线

二次贝塞尔曲线只有一个控制点,曲线只由三个点定义:起点、控制点和终点。 参数公式与立方贝塞尔曲线非常相似,但最高指数为 2,因此曲线是二次多项式:

x(t) = (1 – t)²x₀ + 2t(1 – t)x₁ + t²x₂

y(t) = (1 – t)²y₀ + 2t(1 – t)y₁ + t²y₂

若要向路径添加二次贝塞尔曲线,请使用 QuadTo 方法或具有单独 x 坐标和 y 坐标的 QuadTo 重载:

public void QuadTo (SKPoint point1, SKPoint point2)

public void QuadTo (Single x1, Single y1, Single x2, Single y2)

方法将当前位置的曲线作为 point2 控制点添加到其中 point1

可以使用二次曲线页试验二次贝塞尔曲线,这与贝塞尔曲线页非常相似,只不过只有三个接触点。 下面是 QuadraticCurve.xaml.cs 代码隐藏文件中的 PaintSurface 处理程序:

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

    canvas.Clear();

    // Draw path with quadratic Bezier
    using (SKPath path = new SKPath())
    {
        path.MoveTo(touchPoints[0].Center);
        path.QuadTo(touchPoints[1].Center,
                    touchPoints[2].Center);

        canvas.DrawPath(path, strokePaint);
    }

    // Draw tangent lines
    canvas.DrawLine(touchPoints[0].Center.X,
                    touchPoints[0].Center.Y,
                    touchPoints[1].Center.X,
                    touchPoints[1].Center.Y, dottedStrokePaint);

    canvas.DrawLine(touchPoints[1].Center.X,
                    touchPoints[1].Center.Y,
                    touchPoints[2].Center.X,
                    touchPoints[2].Center.Y, dottedStrokePaint);

    foreach (TouchPoint touchPoint in touchPoints)
    {
        touchPoint.Paint(canvas);
    }
}

此处它正在运行:

Triple screenshot of the Quadratic Curve page

虚线与起点和终点的曲线相切,并满足控制点。

如果需要常规形状的曲线,二次贝塞尔是好的,但更需要一个控制点而不是两个控制点的便利性。 二次贝塞尔比任何其他曲线更高效地呈现,这就是为什么它在 Skia 中内部用来呈现椭圆弧的原因。

但是,二次贝塞尔曲线的形状不是椭圆形的,这就是为什么需要多个二次贝塞尔来近似椭圆弧。二次贝塞尔是一个参数的段。

圆锥贝塞尔曲线

圆锥贝塞尔曲线(也称为理性二次贝塞尔曲线)是贝塞尔曲线家族的一个相对最近的补充。 与二次贝塞尔曲线一样,理性二次贝塞尔曲线涉及起点、终点和一个控制点。 但理性二次贝塞尔曲线也需要权重值。 它被称为理性二次函数,因为参数公式涉及比率。

X 和 Y 的参数公式是共享相同分母的比率。 下面是 t 的分母 的公式,范围为 0 到 1,权重值为 w

d(t) = (1 – t)² + 2wt(1 – t) + t²

从理论上讲,理性二次可能涉及三个单独的权重值,其中一个用于三个术语,但这些值可以简化为中间的一个权重值。

X 和 Y 坐标的参数公式与二次贝塞尔的参数公式相似,但中间词还包括权重值,表达式除以分母:

x(t) = ((1 – t)²x₀ + 2wt(1 – t)x₁ + t²x₂)) ÷ d(t)

y(t) = ((1 – t)²y₀ + 2wt(1 – t)y₁ + t²y₂)) ÷ d(t)

理性二次贝塞尔曲线也称为圆锥体,因为它们可以确切地表示任何圆锥部分的段,即双曲、双曲、折线、椭圆和圆。

若要将合理二次贝塞尔曲线添加到路径,请使用 ConicTo 方法或具有单独 x 坐标和 y 坐标的 ConicTo 重载:

public void ConicTo (SKPoint point1, SKPoint point2, Single weight)

public void ConicTo (Single x1, Single y1, Single x2, Single y2, Single weight)

请注意最终 weight 参数。

使用“圆锥曲线”页可以试验这些曲线。 ConicCurvePage 类从 InteractivePage 派生。 ConicCurvePage.xaml 文件实例化 Slider ,以选择介于 –2 和 2 之间的权重值。 ConicCurvePage.xaml.cs 代码隐藏文件创建三个 TouchPoint 对象,处理程序 PaintSurface 只需将具有正切线的结果曲线呈现到控制点:

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

    canvas.Clear();

    // Draw path with conic curve
    using (SKPath path = new SKPath())
    {
        path.MoveTo(touchPoints[0].Center);
        path.ConicTo(touchPoints[1].Center,
                     touchPoints[2].Center,
                     (float)weightSlider.Value);

        canvas.DrawPath(path, strokePaint);
    }

    // Draw tangent lines
    canvas.DrawLine(touchPoints[0].Center.X,
                    touchPoints[0].Center.Y,
                    touchPoints[1].Center.X,
                    touchPoints[1].Center.Y, dottedStrokePaint);

    canvas.DrawLine(touchPoints[1].Center.X,
                    touchPoints[1].Center.Y,
                    touchPoints[2].Center.X,
                    touchPoints[2].Center.Y, dottedStrokePaint);

    foreach (TouchPoint touchPoint in touchPoints)
    {
        touchPoint.Paint(canvas);
    }
}

此处它正在运行:

Triple screenshot of the Conic Curve page

如你所看到的,当权重较高时,控制点似乎将曲线向它拉取更多。 当权重为零时,曲线将变为从起点到终点的直线。

从理论上讲,允许负权重,并导致曲线偏离控制点。 但是,–1 或更低值的权重会导致参数公式中的分母对 t 的特定值变为负值。 可能出于此原因,在 ConicTo 方法中忽略负权重。 圆锥曲线程序允许你设置负权重,但正如你通过试验可以看到的那样,负权重与零的权重相同,并导致直线呈现。

派生控制点和权重很容易使用 ConicTo 该方法将圆弧绘制到(但不包括)半圆。 在下图中,起点和终点的正切线在控制点相交。

A conic arc rendering of a circular arc

可以使用三角来确定控制点与圆中心之间的距离:它是圆的半径除以角 α 的余弦值。 若要在起点和终点之间绘制圆弧,请将权重设置为角的同一余弦值。 请注意,如果角度为 180 度,则切线永远不会满足并且权重为零。 但是,对于小于180度的角度,数学工作很好。

“圆锥圆弧”页对此进行了演示。 ConicCircularArc.xaml 文件实例化用于选择角度的 SliderConicCircularArc.xaml.cs 代码隐藏文件中的 PaintSurface 处理程序计算控制点和权重:

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

    canvas.Clear();

    // Translate to center
    canvas.Translate(info.Width / 2, info.Height / 2);

    // Draw the circle
    float radius = Math.Min(info.Width, info.Height) / 4;
    canvas.DrawCircle(0, 0, radius, blackStroke);

    // Get the value of the Slider
    float angle = (float)angleSlider.Value;

    // Calculate sin and cosine for half that angle
    float sin = (float)Math.Sin(Math.PI * angle / 180 / 2);
    float cos = (float)Math.Cos(Math.PI * angle / 180 / 2);

    // Find the points and weight
    SKPoint point0 = new SKPoint(-radius * sin, radius * cos);
    SKPoint point1 = new SKPoint(0, radius / cos);
    SKPoint point2 = new SKPoint(radius * sin, radius * cos);
    float weight = cos;

    // Draw the points
    canvas.DrawCircle(point0.X, point0.Y, 10, blackFill);
    canvas.DrawCircle(point1.X, point1.Y, 10, blackFill);
    canvas.DrawCircle(point2.X, point2.Y, 10, blackFill);

    // Draw the tangent lines
    canvas.DrawLine(point0.X, point0.Y, point1.X, point1.Y, dottedStroke);
    canvas.DrawLine(point2.X, point2.Y, point1.X, point1.Y, dottedStroke);

    // Draw the conic
    using (SKPath path = new SKPath())
    {
        path.MoveTo(point0);
        path.ConicTo(point1, point2, weight);
        canvas.DrawPath(path, redStroke);
    }
}

如你所看到的,红色所示的路径与为参考显示的基础圆没有视觉差异 ConicTo

Triple screenshot of the Conic Circular Arc page

但将角度设置为 180 度,数学失败。

在这种情况下,ConicTo 不支持负权重,因为在理论上(基于参数公式),圆圈可以通过另一个调用完成,以相同点但权重的负值 ConicTo。 这将允许基于(但不包括)零度和 180 度之间的任何角度创建一个只有两个 ConicTo 曲线的整个圆圈。