Partilhar via


A transformação de rotação

Explore os efeitos e animações possíveis com a transformação de rotação SkiaSharp

Com a transformação de rotação, os objetos gráficos do SkiaSharp se libertam da restrição de alinhamento com os eixos horizontal e vertical:

Texto girado em torno de um centro

Para girar um objeto gráfico em torno do ponto (0, 0), o SkiaSharp suporta um RotateDegrees método e um RotateRadians método:

public void RotateDegrees (Single degrees)

public Void RotateRadians (Single radians)

Um círculo de 360 graus é o mesmo que 2π radianos, por isso é fácil converter entre as duas unidades. Use o que for conveniente. Todas as funções trigonométricas na classe .NET Math usam unidades de radianos.

A rotação é no sentido horário para aumentar os ângulos. (Embora a rotação no sistema de coordenadas cartesianas seja no sentido anti-horário por convenção, a rotação no sentido horário é consistente com as coordenadas Y aumentando como no SkiaSharp.) Ângulos negativos e ângulos maiores que 360 graus são permitidos.

As fórmulas de transformação para rotação são mais complexas do que aquelas para translação e escala. Para um ângulo de α, as fórmulas de transformação são:

x' = x•cos(α) – y•sin(α)

y' = x•sin(α) + y•cos(α)

A página Rotação Básica demonstra o RotateDegrees método. O arquivo BasicRotate.xaml.cs exibe algum texto com sua linha de base centralizada na página e o gira com base em um Slider intervalo de –360 a 360. Aqui está a parte relevante do PaintSurface manipulador:

using (SKPaint textPaint = new SKPaint
{
    Style = SKPaintStyle.Fill,
    Color = SKColors.Blue,
    TextAlign = SKTextAlign.Center,
    TextSize = 100
})
{
    canvas.RotateDegrees((float)rotateSlider.Value);
    canvas.DrawText(Title, info.Width / 2, info.Height / 2, textPaint);
}

Como a rotação é centralizada em torno do canto superior esquerdo da tela, para a maioria dos ângulos definidos neste programa, o texto é girado para fora da tela:

Captura de tela tripla da página Rotação Básica

Muitas vezes, você desejará girar algo centralizado em torno de um ponto de pivô especificado usando estas versões dos RotateDegrees métodos and RotateRadians :

public void RotateDegrees (Single degrees, Single px, Single py)

public void RotateRadians (Single radians, Single px, Single py)

A página Rotação centralizada é exatamente como a Rotação básica, exceto que a versão expandida do RotateDegrees é usada para definir o centro de rotação para o mesmo ponto usado para posicionar o texto:

using (SKPaint textPaint = new SKPaint
{
    Style = SKPaintStyle.Fill,
    Color = SKColors.Blue,
    TextAlign = SKTextAlign.Center,
    TextSize = 100
})
{
    canvas.RotateDegrees((float)rotateSlider.Value, info.Width / 2, info.Height / 2);
    canvas.DrawText(Title, info.Width / 2, info.Height / 2, textPaint);
}

Agora o texto gira em torno do ponto usado para posicionar o texto, que é o centro horizontal da linha de base do texto:

Captura de tela tripla da página Rotação centralizada

Assim como acontece com a versão centralizada do Scale método, a versão centralizada da RotateDegrees chamada é um atalho. Aqui está o método:

RotateDegrees (degrees, px, py);

Essa chamada é equivalente ao seguinte:

canvas.Translate(px, py);
canvas.RotateDegrees(degrees);
canvas.Translate(-px, -py);

Você descobrirá que às vezes pode combinar Translate chamadas com Rotate chamadas. Por exemplo, aqui estão as RotateDegrees chamadas e DrawText na página Rotação Centrada ;

canvas.RotateDegrees((float)rotateSlider.Value, info.Width / 2, info.Height / 2);
canvas.DrawText(Title, info.Width / 2, info.Height / 2, textPaint);

A RotateDegrees chamada é equivalente a duas Translate chamadas e um não centralizado RotateDegrees:

canvas.Translate(info.Width / 2, info.Height / 2);
canvas.RotateDegrees((float)rotateSlider.Value);
canvas.Translate(-info.Width / 2, -info.Height / 2);
canvas.DrawText(Title, info.Width / 2, info.Height / 2, textPaint);

A DrawText chamada para exibir texto em um local específico é equivalente a uma Translate chamada para esse local seguida por DrawText no ponto (0, 0):

canvas.Translate(info.Width / 2, info.Height / 2);
canvas.RotateDegrees((float)rotateSlider.Value);
canvas.Translate(-info.Width / 2, -info.Height / 2);
canvas.Translate(info.Width / 2, info.Height / 2);
canvas.DrawText(Title, 0, 0, textPaint);

As duas chamadas consecutivas Translate se cancelam:

canvas.Translate(info.Width / 2, info.Height / 2);
canvas.RotateDegrees((float)rotateSlider.Value);
canvas.DrawText(Title, 0, 0, textPaint);

Conceitualmente, as duas transformações são aplicadas na ordem oposta à forma como aparecem no código. A DrawText chamada exibe o texto no canto superior esquerdo da tela. A RotateDegrees chamada gira esse texto em relação ao canto superior esquerdo. Em seguida, a Translate chamada move o texto para o centro da tela.

Geralmente, existem várias maneiras de combinar rotação e translação. A página Texto girado cria a seguinte exibição:

Captura de tela tripla da página de texto girado

Aqui está o PaintSurface RotatedTextPage manipulador da classe:

static readonly string text = "    ROTATE";
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    using (SKPaint textPaint = new SKPaint
    {
        Color = SKColors.Black,
        TextSize = 72
    })
    {
        float xCenter = info.Width / 2;
        float yCenter = info.Height / 2;

        SKRect textBounds = new SKRect();
        textPaint.MeasureText(text, ref textBounds);
        float yText = yCenter - textBounds.Height / 2 - textBounds.Top;

        for (int degrees = 0; degrees < 360; degrees += 30)
        {
            canvas.Save();
            canvas.RotateDegrees(degrees, xCenter, yCenter);
            canvas.DrawText(text, xCenter, yText, textPaint);
            canvas.Restore();
        }
    }
}

Os xCenter valores e yCenter indicam o centro da tela. O yText valor é um pouco deslocado disso. Esse valor é a coordenada Y necessária para posicionar o texto de forma que ele fique realmente centralizado verticalmente na página. O for loop então define uma rotação com base no centro da tela. A rotação é em incrementos de 30 graus. O texto é desenhado usando o yText valor. O número de espaços em branco antes da palavra "ROTATE" no text valor foi determinado empiricamente para fazer com que a conexão entre essas 12 cadeias de texto pareça um dodecago.

Uma maneira de simplificar esse código é incrementar o ângulo de rotação em 30 graus a cada vez por meio do loop após a DrawText chamada. Isso elimina a necessidade de chamadas para Save e Restore. Observe que a degrees variável não é mais usada no corpo do for bloco:

for (int degrees = 0; degrees < 360; degrees += 30)
{
    canvas.DrawText(text, xCenter, yText, textPaint);
    canvas.RotateDegrees(30, xCenter, yCenter);
}

Também é possível usar a forma simples de RotateDegrees prefaciar o loop com uma chamada para Translate mover tudo para o centro da tela:

float yText = -textBounds.Height / 2 - textBounds.Top;

canvas.Translate(xCenter, yCenter);

for (int degrees = 0; degrees < 360; degrees += 30)
{
    canvas.DrawText(text, 0, yText, textPaint);
    canvas.RotateDegrees(30);
}

O cálculo modificado yText não incorpora yCentermais . Agora, o DrawText call center do texto verticalmente na parte superior da tela.

Como as transformações são aplicadas conceitualmente de forma oposta à forma como aparecem no código, geralmente é possível começar com transformações mais globais, seguidas por transformações mais locais. Geralmente, essa é a maneira mais fácil de combinar rotação e translação.

Por exemplo, suponha que você queira desenhar um objeto gráfico que gira em torno de seu centro como um planeta girando em seu eixo. Mas você também quer que esse objeto gire em torno do centro da tela, como um planeta girando em torno do sol.

Você pode fazer isso posicionando o objeto no canto superior esquerdo da tela e, em seguida, usando uma animação para girá-lo em torno desse canto. Em seguida, translade o objeto horizontalmente como um raio orbital. Agora aplique uma segunda rotação animada, também em torno da origem. Isso faz com que o objeto gire ao virar da esquina. Agora traduza para o centro da tela.

Aqui está o PaintSurface manipulador que contém essas chamadas de transformação na ordem inversa:

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

    canvas.Clear();

    using (SKPaint fillPaint = new SKPaint
    {
        Style = SKPaintStyle.Fill,
        Color = SKColors.Red
    })
    {
        // Translate to center of canvas
        canvas.Translate(info.Width / 2, info.Height / 2);

        // Rotate around center of canvas
        canvas.RotateDegrees(revolveDegrees);

        // Translate horizontally
        float radius = Math.Min(info.Width, info.Height) / 3;
        canvas.Translate(radius, 0);

        // Rotate around center of object
        canvas.RotateDegrees(rotateDegrees);

        // Draw a square
        canvas.DrawRect(new SKRect(-50, -50, 50, 50), fillPaint);
    }
}

Os revolveDegrees campos e rotateDegrees são animados. Este programa usa uma técnica de animação diferente com base na Xamarin.FormsAnimation classe. (Esta classe é descrita no Capítulo 22 de Download gratuito em PDF de Criando aplicativos móveis com Xamarin.Forms) A OnAppearing substituição cria dois Animation objetos com métodos de retorno de chamada e, em seguida, os chama Commit por uma duração de animação:

protected override void OnAppearing()
{
    base.OnAppearing();

    new Animation((value) => revolveDegrees = 360 * (float)value).
        Commit(this, "revolveAnimation", length: 10000, repeat: () => true);

    new Animation((value) =>
    {
        rotateDegrees = 360 * (float)value;
        canvasView.InvalidateSurface();
    }).Commit(this, "rotateAnimation", length: 1000, repeat: () => true);
}

O primeiro Animation objeto é revolveDegrees animado de 0 graus a 360 graus em 10 segundos. O segundo é animado rotateDegrees de 0 graus a 360 graus a cada 1 segundo e também invalida a superfície para gerar outra chamada para o PaintSurface manipulador. A OnDisappearing substituição cancela estas duas animações:

protected override void OnDisappearing()
{
    base.OnDisappearing();
    this.AbortAnimation("revolveAnimation");
    this.AbortAnimation("rotateAnimation");
}

O programa Ugly Analog Clock (assim chamado porque um relógio analógico mais atraente será descrito em um artigo posterior) usa rotação para desenhar as marcas de minutos e horas do relógio e girar os ponteiros. O programa desenha o relógio usando um sistema de coordenadas arbitrário baseado em um círculo centralizado no ponto (0, 0) com um raio de 100. Ele usa translação e dimensionamento para expandir e centralizar esse círculo na página.

As Translate chamadas and Scale se aplicam globalmente ao relógio, portanto, essas são as primeiras a serem chamadas após a inicialização dos SKPaint objetos:

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

    canvas.Clear();

    using (SKPaint strokePaint = new SKPaint())
    using (SKPaint fillPaint = new SKPaint())
    {
        strokePaint.Style = SKPaintStyle.Stroke;
        strokePaint.Color = SKColors.Black;
        strokePaint.StrokeCap = SKStrokeCap.Round;

        fillPaint.Style = SKPaintStyle.Fill;
        fillPaint.Color = SKColors.Gray;

        // Transform for 100-radius circle centered at origin
        canvas.Translate(info.Width / 2f, info.Height / 2f);
        canvas.Scale(Math.Min(info.Width / 200f, info.Height / 200f));
        ...
    }
}

Existem 60 marcas de dois tamanhos diferentes que devem ser desenhadas em círculo o tempo todo. A DrawCircle chamada desenha esse círculo no ponto (0, –90), que em relação ao centro do relógio corresponde a 12:00. A RotateDegrees chamada incrementa o ângulo de rotação em 6 graus após cada marca de escala. A angle variável é usada apenas para determinar se um círculo grande ou um círculo pequeno é desenhado:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    ...
        // Hour and minute marks
        for (int angle = 0; angle < 360; angle += 6)
        {
            canvas.DrawCircle(0, -90, angle % 30 == 0 ? 4 : 2, fillPaint);
            canvas.RotateDegrees(6);
        }
    ...
    }
}

Por fim, o PaintSurface manipulador obtém a hora atual e calcula os graus de rotação para os ponteiros de horas, minutos e segundos. Cada mão é desenhada na posição 12:00 para que o ângulo de rotação seja relativo a isso:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    ...
        DateTime dateTime = DateTime.Now;

        // Hour hand
        strokePaint.StrokeWidth = 20;
        canvas.Save();
        canvas.RotateDegrees(30 * dateTime.Hour + dateTime.Minute / 2f);
        canvas.DrawLine(0, 0, 0, -50, strokePaint);
        canvas.Restore();

        // Minute hand
        strokePaint.StrokeWidth = 10;
        canvas.Save();
        canvas.RotateDegrees(6 * dateTime.Minute + dateTime.Second / 10f);
        canvas.DrawLine(0, 0, 0, -70, strokePaint);
        canvas.Restore();

        // Second hand
        strokePaint.StrokeWidth = 2;
        canvas.Save();
        canvas.RotateDegrees(6 * dateTime.Second);
        canvas.DrawLine(0, 10, 0, -80, strokePaint);
        canvas.Restore();
    }
}

O relógio é certamente funcional, embora os ponteiros sejam bastante rudimentares:

Captura de tela tripla da página de texto do relógio analógico feio

Para um relógio mais atraente, consulte o artigo Dados de caminho SVG no SkiaSharp.