Teilen über


Drei Möglichkeiten, einen Bogen zu zeichnen

Erfahren Sie, wie Sie Mithilfe von SkiaSharp Bögen auf drei verschiedene Arten definieren.

Ein Bogen ist eine Kurve auf dem Umfang einer Ellipse, z. B. die abgerundeten Teile dieses Unendlichkeitszeichens:

Unendliches Zeichen

Trotz der Einfachheit dieser Definition gibt es keine Möglichkeit, eine Bogenzeichnungsfunktion zu definieren, die jedem Bedarf entspricht, und daher kein Konsens zwischen Grafiksystemen der besten Methode zum Zeichnen eines Bogens. Aus diesem Grund beschränkt sich die SKPath Klasse nicht auf nur einen Ansatz.

SKPath definiert eine AddArc Methode, fünf verschiedene ArcTo Methoden und zwei relative RArcTo Methoden. Diese Methoden sind in drei Kategorien unterteilt, die drei sehr unterschiedliche Ansätze zum Angeben eines Bogens darstellen. Welche Sie verwenden, hängt von den verfügbaren Informationen ab, um den Bogen zu definieren, und wie dieser Bogen mit den anderen Grafiken passt, die Sie zeichnen.

Der Winkelbogen

Der Winkelbogenansatz zum Zeichnen von Bögen erfordert, dass Sie ein Rechteck angeben, das eine Ellipse begrenzt. Der Bogen auf dem Umfang dieser Ellipse wird durch Winkel vom Mittelpunkt der Ellipse angegeben, die den Anfang des Bogens und seine Länge angeben. Zwei verschiedene Methoden zeichnen Winkelbögen. Dies sind die AddArc Methode und die ArcTo Methode:

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

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

Diese Methoden sind identisch mit den Methoden Android AddArc und [ArcTo]xref:Android.Graphics.Path.ArcTo*). Die iOS-Methode AddArc ist ähnlich, ist aber auf Bogen auf den Umfang eines Kreises beschränkt, anstatt auf eine Ellipse generalisiert.

Beide Methoden beginnen mit einem SKRect Wert, der sowohl den Standort als auch die Größe einer Ellipse definiert:

Das Oval, das einen Winkelbogen beginnt

Der Bogen ist Teil des Umfangs dieser Ellipse.

Das startAngle Argument ist ein Winkel im Uhrzeigersinn in Grad relativ zu einer horizontalen Linie, die von der Mitte der Auslassungspunkte nach rechts gezeichnet wird. Das sweepAngle Argument ist relativ zum startAngle. Hier sind startAngle und sweepAngle werte von 60 Grad bzw. 100 Grad:

Die Winkel, die einen Winkelbogen definieren

Der Bogen beginnt am Anfangswinkel. Die Länge wird durch den Aufräumwinkel gesteuert. Der Bogen ist hier rot dargestellt:

Der hervorgehobene Winkelbogen

Die Kurve, die dem Pfad mit der AddArc Methode ArcTo hinzugefügt wurde, ist einfach der Teil des Umfangs der Ellipse:

Der Winkelbogen selbst

sweepAngle Die startAngle Argumente können negativ sein: Der Bogen ist im Uhrzeigersinn für positive Werte von sweepAngle und gegen den Uhrzeigersinn für negative Werte.

AddArc Definiert jedoch keine geschlossene Kontur. Wenn Sie danach aufrufenLineTo, wird eine Linie vom Ende des Bogens bis zum Punkt in der LineTo Methode gezeichnet, und dasselbe gilt für ArcTo.AddArc

AddArc startet automatisch eine neue Kontur und entspricht funktionell einem Aufruf ArcTo mit einem endgültigen Argument von true:

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

Dieses letzte Argument wird aufgerufen forceMoveTo, und es verursacht effektiv einen MoveTo Aufruf am Anfang des Bogens. Das beginnt eine neue Kontur. Dies ist nicht der Fall mit einem letzten Argument von false:

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

Diese Version von ArcTo zeichnet eine Linie von der aktuellen Position bis zum Anfang des Bogens. Dies bedeutet, dass der Bogen irgendwo in der Mitte einer größeren Kontur sein kann.

Auf der Seite "Winkelbogen " können Sie zwei Schieberegler verwenden, um die Anfangs- und Aufräumwinkel anzugeben. Die XAML-Datei instanziiert zwei Slider Elemente und ein SKCanvasView. Der PaintCanvas Handler in der datei AngleArcPage.xaml.cs zeichnet sowohl das Oval als auch den Bogen mit zwei SKPaint Objekten, die als Felder definiert sind:

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

Wie Sie sehen können, können sowohl der Anfangswinkel als auch der Aufräumwinkel negative Werte annehmen:

Dreifacher Screenshot der Seite

Dieser Ansatz zum Generieren eines Bogens ist algorithmisch am einfachsten, und es ist einfach, die parametrischen Gleichungen abzuleiten, die den Bogen beschreiben. Wenn Sie die Größe und Position der Ellipse kennen und die Anfangs- und Aufräumwinkel kennen, können die Anfangs- und Endpunkte des Bogens mit einfacher Trigonometrie berechnet werden:

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

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

Der angle Wert ist entweder startAngle oder startAngle + sweepAngle.

Die Verwendung von zwei Winkeln zum Definieren eines Bogens eignet sich am besten für Fälle, in denen Sie die Winkellänge des Bogens kennen, den Sie zeichnen möchten, z. B. um ein Kreisdiagramm zu erstellen. Auf der Seite "Explodiertes Kreisdiagramm" wird dies veranschaulicht. Die ExplodedPieChartPage Klasse verwendet eine interne Klasse, um einige fabricierte Daten und Farben zu definieren:

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

Der PaintSurface Handler durchläuft zunächst die Elemente, um eine totalValues Zahl zu berechnen. Daraus kann die Größe jedes Elements als Bruchteil der Summe bestimmt und in einen Winkel konvertiert werden:

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

Für jedes Kreissegment wird ein neues SKPath Objekt erstellt. Der Pfad besteht aus einer Linie aus der Mitte, dann einer ArcTo , um den Bogen zu zeichnen, und eine weitere Linie zurück zur Mitte ergebnisse des Close Anrufs. Dieses Programm zeigt "explodierte" Kreissegmente an, indem sie alle von der Mitte um 50 Pixel aus der Mitte bewegen. Für diesen Vorgang ist ein Vektor in Richtung des Mittelpunkts des Aufräumwinkels für jedes Segment erforderlich:

Dreifacher Screenshot der Seite

Um zu sehen, wie es ohne die "Explosion" aussieht, kommentieren Sie einfach den Translate Anruf:

Dreifacher Screenshot der Seite

Der Tangensbogen

Der zweite Von diesem Bogen unterstützte SKPath Bogentyp ist der Tangensbogen, der so genannt wird, weil der Bogen der Umfang eines Kreises ist, der zu zwei verbundenen Linien tangensiert ist.

Ein Tangensbogen wird einem Pfad mit einem Aufruf der ArcTo Methode mit zwei SKPoint Parametern oder der ArcTo Überladung mit separaten Single Parametern für die Punkte hinzugefügt:

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

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

Diese ArcTo Methode ähnelt der PostScript-Funktion arct (Seite 532) und der iOS-Methode AddArcToPoint .

Die ArcTo Methode umfasst drei Punkte:

  • Der aktuelle Punkt der Kontur oder der Punkt (0, 0) wenn MoveTo nicht aufgerufen wurde
  • Das erste Punktargument auf die ArcTo Methode, der als Eckpunkt bezeichnet wird
  • Das zweite Punktargument, das ArcToals Zielpunkt bezeichnet wird:

Drei Punkte, die einen Tangensbogen beginnen

Diese drei Punkte definieren zwei verbundene Linien:

Linien, die die drei Punkte eines Tangentenbogens verbinden

Wenn die drei Punkte kolinear sind , d. h. wenn sie auf derselben geraden Linie liegen , wird kein Bogen gezeichnet.

Die ArcTo Methode enthält auch einen radius Parameter. Dadurch wird der Radius eines Kreises definiert:

Der Kreis eines Tangensbogens

Der Tangensbogen wird für eine Ellipse nicht generalisiert.

Wenn sich die beiden Linien in einem beliebigen Winkel treffen, kann dieser Kreis zwischen diesen Linien eingefügt werden, sodass er für beide Linien tangens ist:

Der Tangensbogenkreis zwischen den beiden Linien

Die Kurve, die der Kontur hinzugefügt wird, berührt keine der in der ArcTo Methode angegebenen Punkte. Sie besteht aus einer geraden Linie vom aktuellen Punkt zum ersten Tangentenpunkt und einem Bogen, der am zweiten Tangentenpunkt endet, der hier in Rot dargestellt wird:

Das Diagramm zeigt das vorherige Diagramm mit einer roten Linie, die den hervorgehobenen Tangensbogen zwischen den beiden Linien anzeigt.

Hier ist die letzte gerade Linie und der Bogen, die der Kontur hinzugefügt wird:

Der hervorgehobene Tangensbogen zwischen den beiden Linien

Die Kontur kann vom zweiten Tangenspunkt fortgesetzt werden.

Mit der Tangensbogenseite können Sie mit dem Tangentenbogen experimentieren. Dies ist die erste von mehreren Seiten, die von InteractivePageabgeleitet werden, die einige praktische SKPaint Objekte definiert und die Verarbeitung durchführt 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();
        }
    }
}

Die TangentArcPage-Klasse wird von InteractivePage abgeleitet. Der Konstruktor in der TangentArcPage.xaml.cs Datei ist für das Instanziieren und Initialisieren des touchPoints Arrays verantwortlich, und die Einstellung baseCanvasView (in InteractivePage) auf das objekt, das SKCanvasView in der TangentArcPage.xaml-Datei instanziiert wird:

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();
        }
    }
    ...
}

Der PaintSurface Handler verwendet die ArcTo Methode, um den Bogen basierend auf den Berührungspunkten und einem Slider, aber auch algorithmisch berechnet den Kreis, auf dem der Winkel basiert:

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

Dies ist die Ausgeführte Tangenten arc-Seite :

Dreifacher Screenshot der Seite Tangens arc

Der Tangensbogen eignet sich ideal zum Erstellen abgerundeter Ecken, z. B. eines abgerundeten Rechtecks. Da SKPath bereits eine AddRoundedRect Methode enthalten ist, veranschaulicht die Rounded Heptagon-Seite , wie ArcTo die Ecken eines siebenseitigen Polygons gerundet werden. (Der Code wird für ein normales Polygon generalisiert.)

Der PaintSurface Handler der RoundedHeptagonPage Klasse enthält eine for Schleife, um die Koordinaten der sieben Scheitelpunkte des Heptagon zu berechnen, und eine Sekunde, um die Mittelpunkte der sieben Seiten aus diesen Scheitelpunkten zu berechnen. Diese Mittelpunkte werden dann zum Erstellen des Pfads verwendet:

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

Dies ist das Programm, das ausgeführt wird:

Dreifacher Screenshot der Seite

Der elliptische Bogen

Der elliptische Bogen wird einem Pfad mit einem Aufruf der ArcTo Methode mit zwei SKPoint Parametern oder der ArcTo Überladung mit separaten X- und Y-Koordinaten hinzugefügt:

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)

Der elliptische Bogen entspricht dem elliptischen Bogen, der in skalierbaren Vektorgrafiken (SVG) und der Universelle Windows-Plattform ArcSegment Klasse enthalten ist.

Diese ArcTo Methoden zeichnen einen Bogen zwischen zwei Punkten, die den aktuellen Punkt der Kontur und den letzten Parameter für die ArcTo Methode (den xy Parameter oder die separaten x und y Parameter) darstellen:

Die beiden Punkte, die einen elliptischen Bogen definiert haben

Der erste Punktparameter für die ArcTo Methode (roder rx und ry) ist überhaupt kein Punkt, sondern gibt stattdessen die horizontalen und vertikalen Radien einer Ellipse an;

Die Auslassungspunkte, die einen elliptischen Bogen definiert haben

Der xAxisRotate Parameter ist die Anzahl der Grad im Uhrzeigersinn, um diese Ellipse zu drehen:

Die gekippten Ellipsen, die einen elliptischen Bogen definiert haben

Wenn diese gekippte Ellipse dann so positioniert ist, dass sie die beiden Punkte berührt, werden die Punkte durch zwei verschiedene Bögen verbunden:

Die erste Gruppe von elliptischen Bögen

Diese beiden Bögen können auf zwei Arten unterschieden werden: Der obere Bogen ist größer als der untere Bogen, und da der Bogen von links nach rechts gezeichnet wird, wird der obere Bogen in eine uhrzeigersinnige Richtung gezeichnet, während der untere Bogen in einer gegen den Uhrzeigersinn gezeichnet wird.

Es ist auch möglich, die Auslassungspunkte zwischen den beiden Punkten auf eine andere Weise zu passen:

Die zweite Gruppe von elliptischen Bögen

Jetzt gibt es einen kleineren Bogen oben, der im Uhrzeigersinn gezeichnet wird, und einen größeren Bogen unten, der gegen den Uhrzeigersinn gezeichnet wird.

Diese beiden Punkte können daher durch einen Bogen verbunden werden, der durch die gekippte Ellipse auf insgesamt vier Arten definiert ist:

Alle vier elliptischen Bögen

Diese vier Bögen werden durch die vier Kombinationen der SKPathArcSize Argumente und SKPathDirection Enumerationstypen der ArcTo Methode unterschieden:

  • rot: SKPathArcSize.Large und SKPathDirection.Clockwise
  • grün: SKPathArcSize.Small und SKPathDirection.Clockwise
  • blau: SKPathArcSize.Small und SKPathDirection.CounterClockwise
  • Magenta: SKPathArcSize.Large und SKPathDirection.CounterClockwise

Wenn die gekippte Ellipse nicht groß genug ist, um zwischen den beiden Punkten zu passen, wird sie gleichmäßig skaliert, bis sie groß genug ist. In diesem Fall verbinden nur zwei eindeutige Bögen die beiden Punkte. Diese können mit dem SKPathDirection Parameter unterschieden werden.

Obwohl dieser Ansatz zum Definieren eines Bogens komplex bei der ersten Begegnung ist, ist es der einzige Ansatz, der das Definieren eines Bogens mit einer gedrehten Ellipse ermöglicht, und es ist oft der einfachste Ansatz, wenn Sie Bögen in andere Teile der Kontur integrieren müssen.

Mit der Elliptical Arc-Seite können Sie die beiden Punkte interaktiv und die Größe und Drehung der Ellipse festlegen. Die EllipticalArcPage Klasse wird von InteractivePage, und der PaintSurface Handler in der EllipticalArcPage.xaml.cs CodeBehind-Datei zeichnet die vier Bögen:

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

Hier wird folgendes ausgeführt:

Dreifacher Screenshot der Seite

Die Arc Infinity-Seite verwendet den elliptischen Bogen, um ein Unendlichkeitszeichen zu zeichnen. Das Unendlichkeitszeichen basiert auf zwei Kreisen mit 100 Einheiten getrennt durch 100 Einheiten:

Zwei Kreise

Zwei linienüberquerende Linien sind tangens für beide Kreise:

Zwei Kreise mit Tangenslinien

Das Unendlichkeitszeichen ist eine Kombination aus Teilen dieser Kreise und den beiden Linien. Um den elliptischen Bogen zu verwenden, um das Unendlichkeitszeichen zu zeichnen, müssen die Koordinaten bestimmt werden, in denen die beiden Linien tangens zu den Kreisen sind.

Erstellen Sie ein rechtes Rechteck in einem der Kreise:

Zwei Kreise mit Tangenslinien und eingebetteten Kreisen

Der Radius des Kreises beträgt 100 Einheiten, und die Hypotenuse des Dreiecks beträgt 150 Einheiten, sodass der Winkel α der Arkussinus (umgekehrter Sinus) von 100 dividiert durch 150 oder 41,8 Grad ist. Die Länge der anderen Seite des Dreiecks beträgt 150 Mal den Kosinus von 41,8 Grad oder 112, die auch vom Pythagorischen Theorem berechnet werden kann.

Die Koordinaten des Tangenspunkts können dann anhand dieser Informationen berechnet werden:

x = 112·cos(41.8) = 83

y = 112·sin(41.8) = 75

Die vier Tangenspunkte sind alle, die erforderlich sind, um ein Unendlichkeitszeichen zu zeichnen, das auf dem Punkt (0, 0) mit Kreisradien von 100 zentriert ist:

Zwei Kreise mit Tangenslinien und Koordinaten

Der PaintSurface Handler in der ArcInfinityPage Klasse positioniert das Unendlichkeitszeichen so, dass der Punkt (0, 0) in der Mitte der Seite positioniert wird, und skaliert den Pfad zur Bildschirmgröße:

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

Der Code verwendet die Bounds Eigenschaft, SKPath um die Dimensionen des Unendlichen Sinus zu bestimmen, um sie auf die Größe des Zeichenbereichs zu skalieren:

Dreifacher Screenshot der Arc Infinity-Seite

Das Ergebnis scheint ein wenig klein zu sein, was darauf hindeutet, dass die Bounds Eigenschaft SKPath eine Größe meldet, die größer als der Pfad ist.

Intern nähert Skia den Bogen mit mehreren quadratischen Bézierkurven an. Diese Kurven (wie sie im nächsten Abschnitt angezeigt werden) enthalten Kontrollpunkte, die steuern, wie die Kurve gezeichnet wird, aber nicht Teil der gerenderten Kurve sind. Die Bounds Eigenschaft enthält diese Kontrollpunkte.

Um eine engere Anpassung zu erhalten, verwenden Sie die TightBounds Eigenschaft, die die Kontrollpunkte ausschließt. Hier sehen Sie das Programm, das im Querformatmodus ausgeführt wird, und verwenden Sie die TightBounds Eigenschaft, um die Pfadgrenzen abzurufen:

Dreifacher Screenshot der Arc Infinity-Seite mit engen Grenzen

Obwohl die Verbindungen zwischen den Bögen und geraden Linien mathematisch glatt sind, kann der Wechsel von Bogen zu gerade Linie etwas abrupt erscheinen. Im nächsten Artikel zu drei Arten von Bézierkurven wird ein besseres Unendlichkeitszeichen vorgestellt.