円弧を描画する 3 つの方法

Download Sampleサンプルのダウンロード

SkiaSharp を使用して円弧を定義するための 3 つの異なる方法の説明

円弧とは、楕円の円周での曲線部であり、たとえば、この無限大記号の丸い部分などのことです。

Infinity sign

その定義は単純なのですが、すべてのニーズを満たす円弧描画関数を定義する方法は存在していません。そのため、グラフィックス システムにおいて、円弧を描画する最適な方法についてのコンセンサスはありません。このため SKPath クラスでは、 1 つだけのアプローチに、それ自体を制限してはいません。

SKPath では、AddArc メソッド、5 つの異なる ArcTo メソッド、および 2 つの相対的な RArcTo メソッドを定義しています。 これらのメソッドは、円弧を指定するための 3 つの非常に異なるアプローチを代表する、3 つのカテゴリに分類されます。どれを使用するかは、円弧の定義に使用できる情報と、この円弧が他に描画しているグラフィックスにどのように適合するかによって異なります。

角度円弧

角度円弧で円弧を描画するアプローチでは、楕円を囲む四角形を指定する必要があります。 この楕円の円周上の円弧は、円弧の始点とその長さを示す、楕円の中心からの角度によって表されます。 2 つの異なるメソッドが、角度円弧を描画します。 これら 2 つとは、AddArc メソッドと ArcTo メソッドです。

public void AddArc (SKRect oval, Single startAngle, Single sweepAngle)

public void ArcTo (SKRect oval, Single startAngle, Single sweepAngle, Boolean forceMoveTo)

これらのメソッドは、Android の AddArc および [ArcTo]xref:Android.Graphics.Path.ArcTo*) メソッドと同じです。 iOS の AddArc メソッドは似ていますが、楕円として一般化されてはいなく、円の円周での円弧に制限されています。

どのメソッドも、楕円の位置とサイズの両方を定義する SKRect 値を最初に定義します。

The oval that begins an angle arc

円弧は、この楕円の円周の一部です。

startAngle 引数は、楕円の中心から右に描画された水平線を基準にした時計回りの相対角度で、度数で表します。 sweepAngle 引数は、startAngle に対し相対的です。 ここでの startAnglesweepAngle は、それぞれ 60 度と100 度の値を取っています。

The angles that define an angle arc

円弧は開始角度が起点になります。 その長さは掃引 (スイープ) 角度によって制御されます。 ここでは、円弧を赤色で示しています。

The highlighted angle arc

AddArc または ArcTo メソッドを使用 してこのパスに追加される曲線は、単純に楕円の円周のその部分を示します。

The angle arc by itself

startAngle または sweepAngle 引数は 負の値を取ることができます。sweepAngle が正の値の場合は円弧が時計回りに、負の値の場合は反時計回りになります。

ただし、AddArc は閉じた輪郭線を定義しません。 AddArc の後に LineTo を呼び出すと、円弧の終点から LineTo メソッド内の点までの線が描画されます。これは ArcTo でも同様です。

AddArc では、自動的に新しい輪郭が開始され、機能としては最後の引数に true を指定して ArcTo を呼び出したのと同等です。

path.ArcTo (oval, startAngle, sweepAngle, true);

その最後の引数の名前は forceMoveTo で、これにより、実質的に円弧の開始時に MoveTo が呼び出されるようになります。これで新しい輪郭が開始されます。 最後の引数が false の場合、これは実行されません。

path.ArcTo (oval, startAngle, sweepAngle, false);

このバージョンの ArcTo では、現在の位置から円弧の開始点までの線が描画されます。つまり円弧を、より大きな輪郭の中間のどこかに配置することができます。

[Angle Arc] ページでは、2 つのスライダーを使用して、開始の角度と掃引する角度を指定できます。 XAML ファイルは、2 つの Slider 要素と 1 つの SKCanvasView 要素をインスタンス化します。 AngleArcPage.xaml.cs ファイル内の PaintCanvas ハンドラーは、フィールドとして定義された 2 つの SKPaint オブジェクトを使用して、楕円と円弧の両方を描画できます。

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

    canvas.Clear();

    SKRect rect = new SKRect(100, 100, info.Width - 100, info.Height - 100);
    float startAngle = (float)startAngleSlider.Value;
    float sweepAngle = (float)sweepAngleSlider.Value;

    canvas.DrawOval(rect, outlinePaint);

    using (SKPath path = new SKPath())
    {
        path.AddArc(rect, startAngle, sweepAngle);
        canvas.DrawPath(path, arcPaint);
    }
}

ごこで示しているように、開始角度と掃引角度の両方とも、負の値を取ることができます。

Triple screenshot of the Angle Arc page

円弧を生成するためのこの方法は、アルゴリズム的に最も簡単であり、円弧を記述する媒介変数方程式を簡単に導き出せます。楕円のサイズと位置、開始角度と掃引角度を把握することで、単純な三角法を使用して円弧の始点と終点が計算できます。

x = oval.MidX + (oval.Width / 2) * cos(angle)

y = oval.MidY + (oval.Height / 2) * sin(angle)

angle 値は、startAngle または startAngle + sweepAngle のどちらかです。

円弧の定義に 2 つの角度を使用することは、たとえば円グラフを作成する場合など、描画する円弧の角度的な長さを把握している場合に最適です。 [Exploded Pie Chart] ページでこれを示します。 ExplodedPieChartPage クラスでは、内部クラスを使用して、データと色の組み合わせをいくつか定義します。

class ChartData
{
    public ChartData(int value, SKColor color)
    {
        Value = value;
        Color = color;
    }

    public int Value { private set; get; }

    public SKColor Color { private set; get; }
}

ChartData[] chartData =
{
    new ChartData(45, SKColors.Red),
    new ChartData(13, SKColors.Green),
    new ChartData(27, SKColors.Blue),
    new ChartData(19, SKColors.Magenta),
    new ChartData(40, SKColors.Cyan),
    new ChartData(22, SKColors.Brown),
    new ChartData(29, SKColors.Gray)
};

PaintSurface ハンドラーは、最初に各項目をループ処理し、totalValues の数を計算します。 その後は、合計に対する比率として各項目のサイズを決定し、角度に変換できます。

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

    canvas.Clear();

    int totalValues = 0;

    foreach (ChartData item in chartData)
    {
        totalValues += item.Value;
    }

    SKPoint center = new SKPoint(info.Width / 2, info.Height / 2);
    float explodeOffset = 50;
    float radius = Math.Min(info.Width / 2, info.Height / 2) - 2 * explodeOffset;
    SKRect rect = new SKRect(center.X - radius, center.Y - radius,
                             center.X + radius, center.Y + radius);

    float startAngle = 0;

    foreach (ChartData item in chartData)
    {
        float sweepAngle = 360f * item.Value / totalValues;

        using (SKPath path = new SKPath())
        using (SKPaint fillPaint = new SKPaint())
        using (SKPaint outlinePaint = new SKPaint())
        {
            path.MoveTo(center);
            path.ArcTo(rect, startAngle, sweepAngle, false);
            path.Close();

            fillPaint.Style = SKPaintStyle.Fill;
            fillPaint.Color = item.Color;

            outlinePaint.Style = SKPaintStyle.Stroke;
            outlinePaint.StrokeWidth = 5;
            outlinePaint.Color = SKColors.Black;

            // Calculate "explode" transform
            float angle = startAngle + 0.5f * sweepAngle;
            float x = explodeOffset * (float)Math.Cos(Math.PI * angle / 180);
            float y = explodeOffset * (float)Math.Sin(Math.PI * angle / 180);

            canvas.Save();
            canvas.Translate(x, y);

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

        startAngle += sweepAngle;
    }
}

円グラフの各片ごとに、新しい SKPath オブジェクトが作成されます。 パスは、中心からの線、円弧を描画する ArcTo、および Close を呼び出した結果として中央に戻る別の線で構成されます。 このプログラムでは、"分割" された円のスライスを、中央から 50 ピクセル離れた位置に移動させ表示します。 このタスクには、各片の掃引角度の中間点に向かうベクトルが必要です。

Triple screenshot of the Exploded Pie Chart page

"分割" なしで表示される内容を確認するには、単純に Translate の呼び出しをコメントアウトします。

Triple screenshot of the Exploded Pie Chart page without the explosion

正接円弧

SKPath で 2 番目にサポートされる円弧の種類は、正接円弧です。円弧が 2 本の接続された線に接する円の円周であるため、このように呼ばれています。

正接円弧は、2 つの SKPoint パラメータを持つ ArcTo メソッドの呼び出しを使用してパスに追加されます。または、各点の個別の Single パラメータを使用して、ArcTo でオーバーロードされます。

public void ArcTo (SKPoint point1, SKPoint point2, Single radius)

public void ArcTo (Single x1, Single y1, Single x2, Single y2, Single radius)

この ArcTo メソッドは、PostScript arct 関数 (532 ページ) と iOS の AddArcToPoint メソッドに似ています。

この ArcTo メソッドには、次の 3 つの点が関係します。

  • 輪郭の現在の点、または MoveTo が呼び出されていない場合には、(0, 0) の点
  • ArcTo メソッドの 最初の点の引数は、コーナー ポイントと呼ばれます
  • ArcTo に渡される 2 番目の点の引数は、移動先ポイントと呼ばれます。

Three points that begin a tangent arc

次の 3 つの点によって、2 つの接続された線が定義されます。

Lines connecting the three points of a tangent arc

3 つの点が共線、つまり、同じ直線上にある場合、円弧は描画されません。

また、ArcTo メソッドには radius パラメータも含まれています。 これにより、円の半径が定義されます。

The circle of a tangent arc

正接円弧は楕円に対して一般化されていません。

2 本の線が任意の角度で一致する場合は、その円をそれらの線の間に挿入できます。つまり、円は両方の線に正接されます。

The tangent arc circle between the two lines

輪郭に追加された曲線は、ArcTo メソッドで指定された点にも接触しません。 これは、現在の点から最初の接線までの直線と、2 番目の接線で終わる円弧で構成されており、ここでは赤で示されています。

Diagram shows the previous diagram annotated with a red line that shows the highlighted tangent arc between the two lines.

次に、輪郭に追加される最終的な直線と円弧を示します。

The highlighted tangent arc between the two lines

この輪郭は、2 番目の正接点から継続することができます。

[Tangent Arc] ページでは、正接円弧の実験を行なえます。これは、InteractivePage から 派生する数ページの最初で、いくつかの便利な SKPaint オブジェクトを定義し、さらに TouchPoint の処理を実行します。

public class InteractivePage : ContentPage
{
    protected SKCanvasView baseCanvasView;
    protected TouchPoint[] touchPoints;

    protected SKPaint strokePaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Black,
        StrokeWidth = 3
    };

    protected SKPaint redStrokePaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Red,
        StrokeWidth = 15
    };

    protected SKPaint dottedStrokePaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Black,
        StrokeWidth = 3,
        PathEffect = SKPathEffect.CreateDash(new float[] { 7, 7 }, 0)
    };

    protected void OnTouchEffectAction(object sender, TouchActionEventArgs args)
    {
        bool touchPointMoved = false;

        foreach (TouchPoint touchPoint in touchPoints)
        {
            float scale = baseCanvasView.CanvasSize.Width / (float)baseCanvasView.Width;
            SKPoint point = new SKPoint(scale * (float)args.Location.X,
                                        scale * (float)args.Location.Y);
            touchPointMoved |= touchPoint.ProcessTouchEvent(args.Id, args.Type, point);
        }

        if (touchPointMoved)
        {
            baseCanvasView.InvalidateSurface();
        }
    }
}

TangentArcPage クラスは、InteractivePage から派生したものです。 TangentArcPage.xaml.cs ファイル内の コンストラクターにより、touchPoints 配列がインスタンス化および初期化され、TangentArcPage.xaml ファイルで インスタンス化された SKCanvasView オブジェクトに対し、 (InteractivePage 内の) baseCanvasView が設定されます。

public partial class TangentArcPage : InteractivePage
{
    public TangentArcPage()
    {
        touchPoints = new TouchPoint[3];

        for (int i = 0; i < 3; i++)
        {
            TouchPoint touchPoint = new TouchPoint
            {
                Center = new SKPoint(i == 0 ? 100 : 500,
                                     i != 2 ? 100 : 500)
            };
            touchPoints[i] = touchPoint;
        }

        InitializeComponent();

        baseCanvasView = canvasView;
        radiusSlider.Value = 100;
    }

    void sliderValueChanged(object sender, ValueChangedEventArgs args)
    {
        if (canvasView != null)
        {
            canvasView.InvalidateSurface();
        }
    }
    ...
}

PaintSurface ハンドラーは ArcTo メソッドを使用して、接点と Sliderに基づいて円弧を描画しますが、角度の基になっている円についても、アルゴリズム的な計算を実行します。

public partial class TangentArcPage : InteractivePage
{
    ...
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        // Draw the two lines that meet at an angle
        using (SKPath path = new SKPath())
        {
            path.MoveTo(touchPoints[0].Center);
            path.LineTo(touchPoints[1].Center);
            path.LineTo(touchPoints[2].Center);
            canvas.DrawPath(path, dottedStrokePaint);
        }

        // Draw the circle that the arc wraps around
        float radius = (float)radiusSlider.Value;

        SKPoint v1 = Normalize(touchPoints[0].Center - touchPoints[1].Center);
        SKPoint v2 = Normalize(touchPoints[2].Center - touchPoints[1].Center);

        double dotProduct = v1.X * v2.X + v1.Y * v2.Y;
        double angleBetween = Math.Acos(dotProduct);
        float hypotenuse = radius / (float)Math.Sin(angleBetween / 2);
        SKPoint vMid = Normalize(new SKPoint((v1.X + v2.X) / 2, (v1.Y + v2.Y) / 2));
        SKPoint center = new SKPoint(touchPoints[1].Center.X + vMid.X * hypotenuse,
                                     touchPoints[1].Center.Y + vMid.Y * hypotenuse);

        canvas.DrawCircle(center.X, center.Y, radius, this.strokePaint);

        // Draw the tangent arc
        using (SKPath path = new SKPath())
        {
            path.MoveTo(touchPoints[0].Center);
            path.ArcTo(touchPoints[1].Center, touchPoints[2].Center, radius);
            canvas.DrawPath(path, redStrokePaint);
        }

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

    // 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);
    }
}

次に、実行中の [Tangent Arc] ページを示します。

Triple screenshot of the Tangent Arc page

正接円弧は、四角形での面取りなど、丸い角を作成するのに最適です。 [Rounded Heptagon] ページでは、SKPath に既に含まれている AddRoundedRect メソッドにより、ArcTo を使用して 7 辺の多角形の角を丸める方法 を示しています。 (このコードは、通常の多角形に対して一般化されています。)

PaintSurface クラスの RoundedHeptagonPage ハンドラーには、7 つの頂点の座標を計算する 1 つの for ループと、これらの頂点から 7 つの辺の中間点を計算する 2 つ目のループが含まれています。 その後、これらの中間点がパスを構築するために使用されます。

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

    canvas.Clear();

    float cornerRadius = 100;
    int numVertices = 7;
    float radius = 0.45f * Math.Min(info.Width, info.Height);

    SKPoint[] vertices = new SKPoint[numVertices];
    SKPoint[] midPoints = new SKPoint[numVertices];

    double vertexAngle = -0.5f * Math.PI;       // straight up

    // Coordinates of the vertices of the polygon
    for (int vertex = 0; vertex < numVertices; vertex++)
    {
        vertices[vertex] = new SKPoint(radius * (float)Math.Cos(vertexAngle),
                                       radius * (float)Math.Sin(vertexAngle));
        vertexAngle += 2 * Math.PI / numVertices;
    }

    // Coordinates of the midpoints of the sides connecting the vertices
    for (int vertex = 0; vertex < numVertices; vertex++)
    {
        int prevVertex = (vertex + numVertices - 1) % numVertices;
        midPoints[vertex] = new SKPoint((vertices[prevVertex].X + vertices[vertex].X) / 2,
                                        (vertices[prevVertex].Y + vertices[vertex].Y) / 2);
    }

    // Create the path
    using (SKPath path = new SKPath())
    {
        // Begin at the first midpoint
        path.MoveTo(midPoints[0]);

        for (int vertex = 0; vertex < numVertices; vertex++)
        {
            SKPoint nextMidPoint = midPoints[(vertex + 1) % numVertices];

            // Draws a line from the current point, and then the arc
            path.ArcTo(vertices[vertex], nextMidPoint, cornerRadius);

            // Connect the arc with the next midpoint
            path.LineTo(nextMidPoint);
        }
        path.Close();

        // Render the path in the center of the screen
        using (SKPaint paint = new SKPaint())
        {
            paint.Style = SKPaintStyle.Stroke;
            paint.Color = SKColors.Blue;
            paint.StrokeWidth = 10;

            canvas.Translate(info.Width / 2, info.Height / 2);
            canvas.DrawPath(path, paint);
        }
    }
}

実行中のプログラムを次に示します。

Triple screenshot of the Rounded Heptagon page

楕円円弧

楕円円弧は、2 つの SKPoint パラメータを持つ ArcTo メソッドを 呼び出してパスに追加するか、分離した X 座標と Y 座標使用して ArcTo でオーバーロードされます。

public void ArcTo (SKPoint r, Single xAxisRotate, SKPathArcSize largeArc, SKPathDirection sweep, SKPoint xy)

public void ArcTo (Single rx, Single ry, Single xAxisRotate, SKPathArcSize largeArc, SKPathDirection sweep, Single x, Single y)

この楕円円弧は、スケーラブル ベクター グラフィックス (SVG) およびユニバーサル Windows プラットフォームの ArcSegment クラスに含まれる、楕円円弧 と一致 します。

これらの ArcTo メソッドは、輪郭の現在の点である 2 つの点と、ArcTo メソッドの最後のパラメータ (xy パラメータまたは、x および y に分離したパラメータ) の間に円弧を描画します。

The two points that defined an elliptical arc

ArcTo メソッドへの最初の点のパラメータ (rrxry) は、実は点ではなく、楕円の水平方向と垂直方向の半径を指定するものです。

The ellipse that defined an elliptical arc

xAxisRotate パラメータは、この楕円を回転させる時計回りの度数を表します。

The tilted ellipse that defined an elliptical arc

この傾いた楕円が 2 つの点に接するように配置されている場合、これらの点は 2 つの異なる円弧で接続されます。

The first set of elliptical arcs

この 2 つの円弧は、2 つの見方で区別できます。上の円弧は下の円弧よりも大きく、円弧が左から右に描画される際は、上の円弧が時計回りの方向に描画され、下の円弧が反時計回りの方向に描画されます。

また、別の方法で、この楕円を 2 点間に収めることもできます。

The second set of elliptical arcs

この場合は、上の小さな円弧は時計回りで描画され、下の大きな円弧は反時計回りに描画されます。

以上のように、全部で 4 つの方法により、傾いた楕円によって定義された円弧によって、これら 2 つの点を接続できます。

All four elliptical arcs

これら 4 つの円弧は、ArcTo メソッドへの SKPathArcSizeSKPathDirection 列挙型引数の 4 つの組み合わせによって区別されます。

  • 赤: SKPathArcSize.Large と SKPathDirection.Clockwise
  • 緑: SKPathArcSize.Small と SKPathDirection.Clockwise
  • 青: SKPathArcSize.Small と SKPathDirection.CounterClockwise
  • マゼンタ: SKPathArcSize.Large と SKPathDirection.CounterClockwise

傾いた楕円が 2 つの点の間に合うほど大きくない場合は、十分な大きさになるまで均一に拡大されます。 その場合、2 つの点を接続するのは 2 つの一意の円弧だけです。 これらは SKPathDirection パラメータで区別できます。

初めて接すると、円弧を定義するこのアプローチは複雑に聞こえますが、これが回転楕円で円弧を定義できる唯一のアプローチであり、円弧を輪郭の他の部分と統合する必要がある場合の最も簡単な方法です。

[Elliptical Arc] ページを使用すると、2 つの点と楕円のサイズと回転を対話形式で設定できます。 EllipticalArcPage クラスは InteractivePage から派生されており、EllipticalArcPage.xaml.cs 分離コード ファイル内の PaintSurface ハンドラーによって、これら 4 種類の円弧が描画 されます。

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())
    {
        int colorIndex = 0;
        SKPoint ellipseSize = new SKPoint((float)xRadiusSlider.Value,
                                          (float)yRadiusSlider.Value);
        float rotation = (float)rotationSlider.Value;

        foreach (SKPathArcSize arcSize in Enum.GetValues(typeof(SKPathArcSize)))
            foreach (SKPathDirection direction in Enum.GetValues(typeof(SKPathDirection)))
            {
                path.MoveTo(touchPoints[0].Center);
                path.ArcTo(ellipseSize, rotation,
                           arcSize, direction,
                           touchPoints[1].Center);

                strokePaint.Color = colors[colorIndex++];
                canvas.DrawPath(path, strokePaint);
                path.Reset();
            }
    }

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

次は、これを実行しているようすです。

Triple screenshot of the Elliptical Arc page

[Arc Infinity] ページでは、 楕円の円弧を使用して無限大記号を描画しています。 この無限大記号は、半径が 100 単位であり、100 単位ごとにで区切られた 2 つの円に基づいています。

Two circles

互いに交差する 2 本の線は、これら両方の円に正接しています。

Two circles with tangent lines

この無限大記号は、これらの円と 2 つの線の部分の組み合わせです。 楕円円弧を使用して無限大記号を描画するには、これら 2 本の線が円に接する座標を決定する必要があります。

円の 1 つの中に、適合する四角形を作成します。

Two circles with tangent lines and embedded circle

円の半径は 100 単位で、三角形の斜線は 150 単位であるため、α角度はアークサイン (逆正弦) 100 を 150 で割った値、つまり 41.8 度です。 三角形の反対側の長さは、41.8 度の余弦の 150 倍、つまり 112 であり、ピタゴラスの定理によって計算することもできます。

次に、この情報を使用して、接線の座標を算出できます。

x = 112·cos(41.8) = 83

y = 112·sin(41.8) = 75

4 つの接線ポイントのすべてが、円半径が 100 で、点 (0, 0) を中心とする無限大符号を描画するために必要です。

Two circles with tangent lines and coordinates

ArcInfinityPage クラス内の PaintSurface ハンドラーは、(0, 0) の点がページの中央になるように無限大記号を配置し、画面サイズに合わせてパスをスケーリングします。

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.LineTo(83, 75);
        path.ArcTo(100, 100, 0, SKPathArcSize.Large, SKPathDirection.CounterClockwise, 83, -75);
        path.LineTo(-83, 75);
        path.ArcTo(100, 100, 0, SKPathArcSize.Large, SKPathDirection.Clockwise, -83, -75);
        path.Close();

        // Use path.TightBounds for coordinates without control points
        SKRect pathBounds = path.Bounds;

        canvas.Translate(info.Width / 2, info.Height / 2);
        canvas.Scale(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);
        }
    }
}

このコードでは、SKPathBounds プロパティ を使用して無限サインの大きさを決定し、キャンバスのサイズにスケーリングしています。

Triple screenshot of the Arc Infinity page

この結果は少し小さいように感じられます。これは、SKPathBounds プロパティが、パスよりも大きなサイズを表していることを示唆しています。

Skia は内部的に、複数の 2 次ベジエ曲線を使用して円弧を近似します。 これらの (次のセクションで示される) 曲線には、曲線の描画方法を制御するコントロール ポイントが含まれていますが、これらはレンダリングされた曲線の一部ではありません。 Bounds プロパティが、これらのコントロール ポイントを含んでいます。

より厳密に適合させるには、TightBounds プロパティを使用し、コントロール ポイントを除外します。 次に、横モードで実行され、TightBounds プロパティを使用してパスの境界を取得するプログラムを示します。

Triple screenshot of the Arc Infinity page with tight bounds

円弧と直線の間の接続は数学的に滑らかですが、円弧から直線への変化が少し急に見えることがあります。 「3 種類のベジエ曲線」での次の記事では、より優れた無限大記号を紹介しています。