Transformación de traslación

Aprenda a usar la transformación translate para desplazar gráficos de SkiaSharp

El tipo más sencillo de transformación de SkiaSharp es la transformación translate o translation. Esta transformación desplaza los objetos gráficos en las direcciones horizontales y verticales. En cierto sentido, la traducción es la transformación más innecesaria porque normalmente se puede lograr el mismo efecto cambiando simplemente las coordenadas que se usan en la función de dibujo. Sin embargo, al representar una ruta de acceso, todas las coordenadas se encapsulan en la ruta de acceso, por lo que es mucho más fácil aplicar una transformación translate para desplazar toda la ruta de acceso.

La traducción también es útil para la animación y para efectos de texto simples:

Sombra de texto, grabado y relieve con traducción

El método Translate de SKCanvas tiene dos parámetros que hacen que los objetos gráficos dibujados posteriormente se desplacen horizontal y verticalmente:

public void Translate (Single dx, Single dy)

Estos argumentos pueden ser negativos. Un segundo método Translate combina los dos valores translation en un único valor SKPoint:

public void Translate (SKPoint point)

La página Translate acumulado del programa de ejemplo muestra que varias llamadas del método Translate son acumulativas. La clase AccumulatedTranslatePage muestra 20 versiones del mismo rectángulo, cada una desplazada del rectángulo anterior lo suficiente para que se extiendan a lo largo de la diagonal. Este es el controlador de eventos PaintSurface:

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())
    {
        strokePaint.Color = SKColors.Black;
        strokePaint.Style = SKPaintStyle.Stroke;
        strokePaint.StrokeWidth = 3;

        int rectangleCount = 20;
        SKRect rect = new SKRect(0, 0, 250, 250);
        float xTranslate = (info.Width - rect.Width) / (rectangleCount - 1);
        float yTranslate = (info.Height - rect.Height) / (rectangleCount - 1);

        for (int i = 0; i < rectangleCount; i++)
        {
            canvas.DrawRect(rect, strokePaint);
            canvas.Translate(xTranslate, yTranslate);
        }
    }
}

Los sucesivos rectángulos se deslizan por la página:

Captura de pantalla triple de la página Traducción acumulada

Si los factores de traducción acumulados son dx y dy, y el punto especificado en una función de dibujo es (x, y), el objeto gráfico se representa en el punto (x', y'), donde:

x' = x + dx

y' = y + dy

Se conocen como fórmulas de transformación para la traducción. Los valores predeterminados de dx y dy para un nuevo SKCanvas son 0.

Es habitual usar la transformación translate para efectos de sombra y técnicas similares, como se muestra en la página Translate Text Effects (Traducir efectos de texto). Esta es la parte pertinente del controlador PaintSurface en la clase TranslateTextEffectsPage:

float textSize = 150;

using (SKPaint textPaint = new SKPaint())
{
    textPaint.Style = SKPaintStyle.Fill;
    textPaint.TextSize = textSize;
    textPaint.FakeBoldText = true;

    float x = 10;
    float y = textSize;

    // Shadow
    canvas.Translate(10, 10);
    textPaint.Color = SKColors.Black;
    canvas.DrawText("SHADOW", x, y, textPaint);
    canvas.Translate(-10, -10);
    textPaint.Color = SKColors.Pink;
    canvas.DrawText("SHADOW", x, y, textPaint);

    y += 2 * textSize;

    // Engrave
    canvas.Translate(-5, -5);
    textPaint.Color = SKColors.Black;
    canvas.DrawText("ENGRAVE", x, y, textPaint);
    canvas.ResetMatrix();
    textPaint.Color = SKColors.White;
    canvas.DrawText("ENGRAVE", x, y, textPaint);

    y += 2 * textSize;

    // Emboss
    canvas.Save();
    canvas.Translate(5, 5);
    textPaint.Color = SKColors.Black;
    canvas.DrawText("EMBOSS", x, y, textPaint);
    canvas.Restore();
    textPaint.Color = SKColors.White;
    canvas.DrawText("EMBOSS", x, y, textPaint);
}

En cada uno de los tres ejemplos, se llama a Translate para mostrar el texto para desplazarlo desde la ubicación dada por las variables x y y. A continuación, el texto se vuelve a mostrar en otro color sin ningún efecto de traducción:

Captura de pantalla triple de la página Traducir efectos de texto

Cada uno de los tres ejemplos muestra una manera diferente de negar la llamada Translate:

El primer ejemplo vuelve a llamar a Translate, pero con valores negativos. Dado que las llamadas Translate son acumulativas, esta secuencia de llamadas restaura la traducción total a los valores predeterminados de cero.

En el segundo ejemplo se llama a ResetMatrix. Esto hace que todas las transformaciones vuelvan a su estado predeterminado.

En el tercer ejemplo se guarda el estado del objeto SKCanvas con una llamada a Save y, a continuación, se restaura el estado con una llamada a Restore. Esta es la manera más versátil de manipular transformaciones para una serie de operaciones de dibujo. Estas llamadas Save y Restore funcionan como una pila: puede llamar varias veces a Save y, a continuación, llamar a Restore en secuencia inversa para volver a los estados anteriores. El método Save devuelve un entero, que puede pasar a RestoreToCount para llamar eficazmente a Restore varias veces. La propiedad SaveCount devuelve el número de estados guardados actualmente en la pila.

También puede usar la clase SKAutoCanvasRestore para restaurar el estado del lienzo. El constructor de esta clase está pensado para llamarse en una instrucción using; el estado del lienzo se restaura automáticamente al final del bloque using.

Sin embargo, no tiene que preocuparse por las transformaciones que se transfieren de una llamada del controlador PaintSurface a la siguiente. Cada nueva llamada a PaintSurface proporciona un objeto SKCanvas nuevo con transformaciones predeterminadas.

Otro uso común de la transformación Translate es el de representar un objeto visual creado originalmente mediante coordenadas que son convenientes para dibujar. Por ejemplo, puede especificar coordenadas para un reloj analógico con un centro en el punto (0, 0). Después, puede usar transformaciones para mostrar el reloj donde desee. Esta técnica se muestra en la página [Hendecagram Array] (Matriz de hendecagramas). La clase HendecagramArrayPage comienza creando un objeto SKPath para una estrella de 11 puntas. El objeto HendecagramPath se define como público, estático y de solo lectura para que se pueda acceder a él desde otros programas de demostración. Se crea en un constructor estático:

public class HendecagramArrayPage : ContentPage
{
    ...
    public static readonly SKPath HendecagramPath;

    static HendecagramArrayPage()
    {
        // Create 11-pointed star
        HendecagramPath = new SKPath();
        for (int i = 0; i < 11; i++)
        {
            double angle = 5 * i * 2 * Math.PI / 11;
            SKPoint pt = new SKPoint(100 * (float)Math.Sin(angle),
                                    -100 * (float)Math.Cos(angle));
            if (i == 0)
            {
                HendecagramPath.MoveTo(pt);
            }
            else
            {
                HendecagramPath.LineTo(pt);
            }
        }
        HendecagramPath.Close();
    }
}

Si el centro de la estrella es el punto (0, 0), todos sus puntos están en un círculo que rodea ese punto Cada punta es una combinación de valores de seno y coseno de un ángulo que aumenta en 5/11 partes de 360 grados. (también es posible crear una estrella de 11 puntas aumentando el ángulo en 2/11, 3/11s o 4/11 del círculo). El radio de ese círculo se establece en 100.

Si esta ruta de acceso se representa sin ninguna transformación, el centro se colocará en la esquina superior izquierda de SKCanvas, y solo será visible un cuarto de él. En su lugar, el controlador PaintSurface de HendecagramPage usa Translate para colocar en mosaico el lienzo con varias copias de la estrella, cada una de ellas coloreada aleatoriamente:

public class HendecagramArrayPage : ContentPage
{
    Random random = new Random();
    ...
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        using (SKPaint paint = new SKPaint())
        {
            for (int x = 100; x < info.Width + 100; x += 200)
                for (int y = 100; y < info.Height + 100; y += 200)
                {
                    // Set random color
                    byte[] bytes = new byte[3];
                    random.NextBytes(bytes);
                    paint.Color = new SKColor(bytes[0], bytes[1], bytes[2]);

                    // Display the hendecagram
                    canvas.Save();
                    canvas.Translate(x, y);
                    canvas.DrawPath(HendecagramPath, paint);
                    canvas.Restore();
                }
        }
    }
}

Este es el resultado:

Captura de pantalla triple de la página Matriz de Hendecagrama

Las animaciones suelen implicar transformaciones. La página Hendecagram Animation (Animación de hendecagrama) mueve la estrella de 11 puntas alrededor de un círculo. La clase HendecagramAnimationPage comienza con algunos campos e invalidaciones de los métodos OnAppearing y OnDisappearing para iniciar y detener un temporizador Xamarin.Forms:

public class HendecagramAnimationPage : ContentPage
{
    const double cycleTime = 5000;      // in milliseconds

    SKCanvasView canvasView;
    Stopwatch stopwatch = new Stopwatch();
    bool pageIsActive;
    float angle;

    public HendecagramAnimationPage()
    {
        Title = "Hedecagram Animation";

        canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;
    }

    protected override void OnAppearing()
    {
        base.OnAppearing();
        pageIsActive = true;
        stopwatch.Start();

        Device.StartTimer(TimeSpan.FromMilliseconds(33), () =>
        {
            double t = stopwatch.Elapsed.TotalMilliseconds % cycleTime / cycleTime;
            angle = (float)(360 * t);
            canvasView.InvalidateSurface();

            if (!pageIsActive)
            {
                stopwatch.Stop();
            }

            return pageIsActive;
        });
    }

    protected override void OnDisappearing()
    {
        base.OnDisappearing();
        pageIsActive = false;
    }
    ...
}

El campo angle se anima de 0 a 360 grados cada 5 segundos. El controlador PaintSurface usa la propiedad angle de dos maneras: para especificar el tono del color en el método SKColor.FromHsl y como argumento para los métodos Math.Sin y Math.Cos para controlar la ubicación de la estrella:

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

        canvas.Clear();
        canvas.Translate(info.Width / 2, info.Height / 2);
        float radius = (float)Math.Min(info.Width, info.Height) / 2 - 100;

        using (SKPaint paint = new SKPaint())
        {
            paint.Style = SKPaintStyle.Fill;
            paint.Color = SKColor.FromHsl(angle, 100, 50);

            float x = radius * (float)Math.Sin(Math.PI * angle / 180);
            float y = -radius * (float)Math.Cos(Math.PI * angle / 180);
            canvas.Translate(x, y);
            canvas.DrawPath(HendecagramPage.HendecagramPath, paint);
        }
    }
}

El controlador PaintSurface llama al método Translate dos veces, primero para traducir al centro del lienzo y, a continuación, para traducir a la circunferencia de un círculo centrado alrededor (0, 0). El radio del círculo se establece para que sea lo más grande posible mientras mantiene la estrella dentro de los límites de la página:

Captura de pantalla triple de la página Animación de Hendecagrama

Observe que la estrella mantiene la misma orientación mientras gira alrededor del centro de la página. No gira en absoluto. Esa es una tarea para una transformación de rotación.