Compartilhar 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 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), 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 radianos 2π, 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 descendo como em 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 traduçã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 com um 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 Girar Básico

Muitas vezes, você desejará girar algo centrado em torno de um ponto de pivô especificado usando estas versões dos RotateDegrees métodos e 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 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 equivale 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 centralizada;

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 uma não centralizada 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 pelo DrawText 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 anulam:

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 à que 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, há 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 Texto girado

Aqui está o PaintSurface manipulador da RotatedTextPage 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 compensado disso. Esse valor é a coordenada Y necessária para posicionar o texto de modo que ele fique verdadeiramente centralizado verticalmente na página. Em for seguida, o loop 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 parecesse ser um dodecagon.

Uma maneira de simplificar esse código é incrementar o ângulo de rotação em 30 graus cada vez através 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 o . Agora, a chamada centraliza DrawText o texto verticalmente na parte superior da tela.

Como as transformações são conceitualmente aplicadas de forma oposta à forma como aparecem no código, muitas vezes é possível começar com transformações mais globais, seguidas por transformações mais locais. Esta é muitas vezes 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 ao redor desse canto. Em seguida, traduza 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 do Download gratuito em PDF de Criando aplicativos móveis comXamarin.Forms ) A OnAppearing substituição cria dois Animation objetos com métodos de retorno de chamada e, em seguida, os chama Commit para 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 anima revolveDegrees de 0 graus a 360 graus em 10 segundos. O segundo anima rotateDegrees de 0 graus a 360 graus a cada 1 segundo e também invalida a superfície para gerar outra chamada ao 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 a 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árias baseado em um círculo que está centralizado no ponto (0, 0) com um raio de 100. Ele usa tradução e dimensionamento para expandir e centralizar esse círculo na página.

As Translate chamadas e Scale se aplicam globalmente ao relógio, então 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 um 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 seleção. 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);
        }
    ...
    }
}

Finalmente, o PaintSurface manipulador obtém a hora atual e calcula os graus de rotação para a hora, minuto e segundos ponteiros. Cada mão é desenhada na posição 12:00 para que o ângulo de rotação seja relativo àquele:

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 grosseiros:

Captura de tela tripla da página Texto do Relógio Analógico Feio

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