Partilhar via


Três formas de desenhar um arco

Saiba como usar o SkiaSharp para definir arcos de três maneiras diferentes

Um arco é uma curva na circunferência de uma elipse, como as partes arredondadas deste signo do infinito:

Sinal do infinito

Apesar da simplicidade dessa definição, não há como definir uma função de desenho de arco que satisfaça todas as necessidades e, portanto, não há consenso entre os sistemas gráficos da melhor maneira de desenhar um arco. Por isso, a SKPath classe não se restringe a apenas uma abordagem.

SKPath define um AddArc método, cinco métodos diferentes ArcTo e dois métodos relativos RArcTo . Esses métodos se enquadram em três categorias, representando três abordagens muito diferentes para especificar um arco. Qual deles você usa depende das informações disponíveis para definir o arco e como esse arco se encaixa com os outros gráficos que você está desenhando.

O Arco do Ângulo

A abordagem do arco angular para desenhar arcos requer que você especifique um retângulo que limite uma elipse. O arco na circunferência desta elipse é indicado por ângulos do centro da elipse que indicam o início do arco e seu comprimento. Dois métodos diferentes desenham arcos angulares. Estes são o AddArc método e o ArcTo método:

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

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

Esses métodos são idênticos aos métodos Android AddArc e [ArcTo]xref:Android.Graphics.Path.ArcTo*). O método iOS AddArc é semelhante, mas é restrito a arcos na circunferência de um círculo, em vez de generalizado para uma elipse.

Ambos os métodos começam com um SKRect valor que define o local e o tamanho de uma elipse:

O oval que inicia um arco angular

O arco é uma parte da circunferência desta elipse.

O startAngle argumento é um ângulo no sentido horário em graus em relação a uma linha horizontal desenhada do centro da elipse à direita. O sweepAngle argumento é relativo ao startAngle. Aqui estão startAngle e sweepAngle valores de 60 graus e 100 graus, respectivamente:

Os ângulos que definem um arco angular

O arco começa no ângulo inicial. Seu comprimento é regido pelo ângulo de varredura. O arco é mostrado aqui em vermelho:

O arco angular realçado

A curva adicionada ao caminho com o AddArc método or ArcTo é simplesmente a parte da circunferência da elipse:

O arco angular por si só

Os startAngle argumentos ou sweepAngle podem ser negativos: O arco é no sentido horário para valores positivos de sweepAngle e anti-horário para valores negativos.

No entanto, AddArc não define um contorno fechado. Se você chamar LineTo depois AddArc, uma linha é desenhada do final do arco até o ponto no LineTo método, e o mesmo é verdadeiro para ArcTo.

AddArc inicia automaticamente um novo contorno e é funcionalmente equivalente a uma chamada para ArcTo com um argumento final de true:

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

Esse último argumento é chamado forceMoveTode , e efetivamente causa uma MoveTo chamada no início do arco. Começa assim um novo contorno. Não é o caso de um último argumento de false:

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

Esta versão do ArcTo traça uma linha da posição atual até o início do arco. Isso significa que o arco pode estar em algum lugar no meio de um contorno maior.

A página Arco Angular permite usar dois controles deslizantes para especificar os ângulos de início e varredura. O arquivo XAML instancia dois Slider elementos e um SKCanvasViewarquivo . O PaintCanvas manipulador no arquivo AngleArcPage.xaml.cs desenha o oval e o arco usando dois SKPaint objetos definidos como campos:

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

Como você pode ver, tanto o ângulo inicial quanto o ângulo de varredura podem assumir valores negativos:

Captura de tela tripla da página Arco Angular

Essa abordagem para gerar um arco é algoritmicamente a mais simples, e é fácil derivar as equações paramétricas que descrevem o arco. Sabendo o tamanho e a localização da elipse, e os ângulos de início e varredura, os pontos inicial e final do arco podem ser calculados usando trigonometria simples:

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

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

O angle valor é ou startAngle startAngle + sweepAngle.

O uso de dois ângulos para definir um arco é melhor para casos em que você sabe o comprimento angular do arco que deseja desenhar, por exemplo, para fazer um gráfico de pizza. A página Gráfico de pizza explodido demonstra isso. A ExplodedPieChartPage classe usa uma classe interna para definir alguns dados e cores fabricados:

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

O PaintSurface manipulador primeiro faz um loop pelos itens para calcular um totalValues número. A partir disso, ele pode determinar o tamanho de cada item como a fração do total e convertê-lo em um ângulo:

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

Um novo SKPath objeto é criado para cada fatia de pizza. O caminho consiste em uma linha do centro, depois uma ArcTo para desenhar o arco e outra linha de volta para o centro resulta da Close chamada. Este programa exibe fatias de torta "explodidas", movendo-as todas para fora do centro por 50 pixels. Essa tarefa requer um vetor na direção do ponto médio do ângulo de varredura para cada fatia:

Captura de tela tripla da página Gráfico de pizza explodido

Para ver como fica sem a "explosão", basta comentar a Translate chamada:

Captura de tela tripla da página Gráfico de pizza explodido sem a explosão

O Arco Tangente

O segundo tipo de arco suportado por SKPath é o arco tangente, assim chamado porque o arco é a circunferência de um círculo que é tangente a duas linhas conectadas.

Um arco tangente é adicionado a um caminho com uma chamada para o ArcTo método com dois SKPoint parâmetros, ou a ArcTo sobrecarga com parâmetros separados Single para os pontos:

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

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

Esse ArcTo método é semelhante à função PostScript arct (página 532) e ao método iOS AddArcToPoint .

O ArcTo método envolve três pontos:

  • O ponto atual do contorno, ou o ponto (0, 0) se MoveTo não tiver sido chamado
  • O primeiro argumento de ponto para o ArcTo método, chamado de ponto de canto
  • O segundo argumento de ponto para ArcTo, chamou o ponto de destino:

Três pontos que iniciam um arco tangente

Estes três pontos definem duas linhas conectadas:

Linhas que ligam os três pontos de um arco tangente

Se os três pontos forem colineares — isto é, se estiverem na mesma reta — nenhum arco será desenhado.

O ArcTo método também inclui um radius parâmetro. Isso define o raio de um círculo:

O círculo de um arco tangente

O arco tangente não é generalizado para uma elipse.

Se as duas linhas se encontrarem em qualquer ângulo, esse círculo pode ser inserido entre essas linhas de modo que seja tangente a ambas as linhas:

O círculo de arco tangente entre as duas linhas

A curva adicionada ao contorno não toca em nenhum dos pontos especificados no ArcTo método. Consiste em uma linha reta do ponto atual até o primeiro ponto tangente, e um arco que termina no segundo ponto tangente, mostrado aqui em vermelho:

O diagrama mostra o diagrama anterior anotado com uma linha vermelha que mostra o arco tangente realçado entre as duas linhas.

Aqui está a reta final e o arco que é adicionado ao contorno:

O arco tangente destacado entre as duas linhas

O contorno pode ser continuado a partir do segundo ponto tangente.

A página Arco Tangente permite que você experimente o arco tangente. Esta é a primeira de várias páginas que derivam do InteractivePage, que define alguns objetos úteis SKPaint e executa TouchPoint o processamento:

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

A classe TangentArcPage deriva de InteractivePage. O construtor no arquivo TangentArcPage.xaml.cs é responsável por instanciar e inicializar a touchPoints matriz e definir baseCanvasView (em InteractivePage) para o SKCanvasView objeto instanciado no arquivo TangentArcPage.xaml:

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

O PaintSurface manipulador usa o ArcTo método para desenhar o arco com base nos pontos de toque e um Slider, mas também calcula algoritmicamente o círculo no qual o ângulo é baseado:

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

Aqui está a página Tangent Arc em execução:

Captura de tela tripla da página Arco Tangente

O arco tangente é ideal para criar cantos arredondados, como um retângulo arredondado. Como SKPath já inclui um AddRoundedRect método, a página Heptagon arredondado demonstra como usar ArcTo para arredondar os cantos de um polígono de sete lados. (O código é generalizado para qualquer polígono regular.)

O PaintSurface manipulador da RoundedHeptagonPage classe contém um for loop para calcular as coordenadas dos sete vértices do heptagon, e um segundo para calcular os pontos médios dos sete lados a partir desses vértices. Esses pontos médios são usados para construir o caminho:

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

Este é o programa em execução:

Captura de tela tripla da página Heptagon arredondado

O Arco Elíptico

O arco elíptico é adicionado a um caminho com uma chamada para o ArcTo método que tem dois SKPoint parâmetros, ou a ArcTo sobrecarga com coordenadas X e Y separadas:

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)

O arco elíptico é consistente com o arco elíptico incluído no SVG (Scalable Vector Graphics) e na classe Universal Windows Platform ArcSegment .

Esses ArcTo métodos desenham um arco entre dois pontos, que são o ponto atual do contorno, e o último parâmetro para o ArcTo método (o xy parâmetro ou o separado x e y parâmetros):

Os dois pontos que definiram um arco elíptico

O primeiro parâmetro de ponto para o ArcTo método (r, ou rx e ry) não é um ponto, mas especifica os raios horizontais e verticais de uma elipse;

A elipse que definiu um arco elíptico

O xAxisRotate parâmetro é o número de graus no sentido horário para girar essa elipse:

A elipse inclinada que definiu um arco elíptico

Se essa elipse inclinada for então posicionada de modo que toque os dois pontos, os pontos serão conectados por dois arcos diferentes:

O primeiro conjunto de arcos elípticos

Esses dois arcos podem ser distinguidos de duas maneiras: O arco superior é maior do que o arco inferior, e como o arco é desenhado da esquerda para a direita, o arco superior é desenhado no sentido horário, enquanto o arco inferior é desenhado no sentido anti-horário.

Também é possível encaixar a elipse entre os dois pontos de outra forma:

O segundo conjunto de arcos elípticos

Agora há um arco menor na parte superior que é desenhado no sentido horário, e um arco maior na parte inferior que é desenhado no sentido anti-horário.

Esses dois pontos podem, portanto, ser conectados por um arco definido pela elipse inclinada em um total de quatro maneiras:

Todos os quatro arcos elípticos

Esses quatro arcos são distinguidos pelas quatro combinações dos argumentos do SKPathArcSize tipo e SKPathDirection enumeração para o ArcTo método:

  • vermelho: SKPathArcSize.Large e SKPathDirection.Clockwise
  • verde: SKPathArcSize.Small e SKPathDirection.Clockwise
  • azul: SKPathArcSize.Small e SKPathDirection.CounterClockwise
  • magenta: SKPathArcSize.Large e SKPathDirection.CounterClockwise

Se a elipse inclinada não for grande o suficiente para caber entre os dois pontos, então ela é uniformemente dimensionada até que seja grande o suficiente. Apenas dois arcos únicos conectam os dois pontos nesse caso. Estes podem ser distinguidos com o SKPathDirection parâmetro.

Embora essa abordagem para definir um arco pareça complexa no primeiro encontro, é a única abordagem que permite definir um arco com uma elipse rotacionada, e geralmente é a abordagem mais fácil quando você precisa integrar arcos com outras partes do contorno.

A página Arco Elíptico permite que você defina interativamente os dois pontos e o tamanho e a rotação da elipse. A EllipticalArcPage classe deriva de InteractivePage, e o PaintSurface manipulador no arquivo code-behind EllipticalArcPage.xaml.cs desenha os quatro arcos:

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

Aqui está em execução:

Captura de tela tripla da página Arco Elíptico

A página Arc Infinity usa o arco elíptico para desenhar um sinal de infinito. O sinal do infinito é baseado em dois círculos com raios de 100 unidades separados por 100 unidades:

Dois círculos

Duas linhas que se cruzam são tangente aos dois círculos:

Dois círculos com linhas tangentes

O signo do infinito é uma combinação de partes desses círculos e das duas linhas. Para usar o arco elíptico para desenhar o sinal do infinito, as coordenadas onde as duas linhas são tangente aos círculos devem ser determinadas.

Construa um retângulo reto em um dos círculos:

Dois círculos com linhas tangentes e círculo embutido

O raio do círculo é de 100 unidades, e a hipotenusa do triângulo é de 150 unidades, então o ângulo α é o arco seno (senoe inverso) de 100 dividido por 150, ou 41,8 graus. O comprimento do outro lado do triângulo é 150 vezes o cosseno de 41,8 graus, ou 112, que também pode ser calculado pelo teorema de Pitágoras.

As coordenadas do ponto tangente podem então ser calculadas usando estas informações:

x = 112·cos(41.8) = 83

y = 112·sin(41.8) = 75

Os quatro pontos tangentes são tudo o que é necessário para desenhar um sinal de infinito centrado no ponto (0, 0) com raios circulares de 100:

Dois círculos com linhas tangentes e coordenadas

O PaintSurface manipulador na ArcInfinityPage classe posiciona o sinal de infinito para que o ponto (0, 0) seja posicionado no centro da página e dimensiona o caminho para o tamanho da tela:

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

O código usa a Bounds propriedade de SKPath para determinar as dimensões do seno infinito para dimensioná-lo para o tamanho da tela:

Captura de tela tripla da página Arc Infinity

O resultado parece um pouco pequeno, o que sugere que a Bounds propriedade de SKPath está relatando um tamanho maior do que o caminho.

Internamente, Skia aproxima o arco usando múltiplas curvas quadráticas de Bézier. Essas curvas (como você verá na próxima seção) contêm pontos de controle que governam como a curva é desenhada, mas não fazem parte da curva renderizada. A Bounds propriedade inclui esses pontos de controle.

Para obter um ajuste mais apertado, use a TightBounds propriedade, que exclui os pontos de controle. Aqui está o programa em execução no modo paisagem e usando a TightBounds propriedade para obter os limites de caminho:

Captura de tela tripla da página Arc Infinity com limites apertados

Embora as conexões entre os arcos e as linhas retas sejam matematicamente suaves, a mudança de arco para linha reta pode parecer um pouco abrupta. Um melhor sinal do infinito é apresentado no próximo artigo sobre Três tipos de curvas de Bézier.