Compartir vía


Transformación de escala

Descubra la transformación de escala SkiaSharp para escalar objetos a varios tamaños

Como ha visto en artículo sobre la transformación de traslación, la transformación de traslación puede mover un objeto gráfico de una ubicación a otra. En cambio, la transformación de escala cambia el tamaño del objeto gráfico:

Una palabra alta escalada en tamaño

La transformación de escala también suele hacer que las coordenadas gráficas se muevan a medida que se hacen más grandes.

Anteriormente vio dos fórmulas de transformación que describen los efectos de los factores de traslación de dx y dy:

x' = x + dx

y' = y + dy

Los factores de escala de sx y sy son multiplicativos en lugar de aditivos:

x' = sx · x

y' = sy · y

Los valores predeterminados de los factores de traslación son 0; los valores predeterminados de los factores de escala son 1.

La clase SKCanvas define cuatro métodos Scale. El primer método Scale es para los casos en los que se desea el mismo factor de escalado horizontal y vertical:

public void Scale (Single s)

Esto se conoce como escalado isotrópico, que es el mismo en ambas direcciones. El escalado isotrópico conserva la relación de aspecto del objeto.

El segundo método Scale permite especificar valores diferentes para el escalado horizontal y vertical:

public void Scale (Single sx, Single sy)

Esto da como resultado un escalado anisotrópico. El tercer método Scale combina los dos factores de escalado en un solo valor SKPoint:

public void Scale (SKPoint size)

El cuarto método Scale se describirá en breve.

La página Escalado básico muestra el método Scale. El archivo BasicScalePage.xaml contiene dos elementos Slider que le permiten seleccionar factores de escalado horizontal y vertical entre 0 y 10. El archivo de código subyacente BasicScalePage.xaml.cs usa esos valores para llamar a Scale antes de mostrar un rectángulo redondeado trazado con una línea discontinua y dimensionado para encajar un texto en la esquina superior izquierda del lienzo:

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

    canvas.Clear(SKColors.SkyBlue);

    using (SKPaint strokePaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Red,
        StrokeWidth = 3,
        PathEffect = SKPathEffect.CreateDash(new float[] {  7, 7 }, 0)
    })
    using (SKPaint textPaint = new SKPaint
    {
        Style = SKPaintStyle.Fill,
        Color = SKColors.Blue,
        TextSize = 50
    })
    {
        canvas.Scale((float)xScaleSlider.Value,
                     (float)yScaleSlider.Value);

        SKRect textBounds = new SKRect();
        textPaint.MeasureText(Title, ref textBounds);

        float margin = 10;
        SKRect borderRect = SKRect.Create(new SKPoint(margin, margin), textBounds.Size);
        canvas.DrawRoundRect(borderRect, 20, 20, strokePaint);
        canvas.DrawText(Title, margin, -textBounds.Top + margin, textPaint);
    }
}

Es posible que se pregunte: ¿Cómo afectan los factores de escalado al valor devuelto por el método MeasureText de SKPaint? La respuesta es: no, en absoluto. Scale es un método de SKCanvas. No afecta a nada de lo que haga con un objeto SKPaint hasta que use ese objeto para representar algo en el lienzo.

Como puede ver, todo lo dibujado después de la llamada Scale aumenta proporcionalmente:

Captura de pantalla triple de la página Escala básica

El texto, el ancho de la línea discontinua, la longitud de los guiones de esa línea, el redondeo de las esquinas y el margen de 10 píxeles entre los bordes izquierdo y superior del lienzo y el rectángulo redondeado están sujetos a los mismos factores de escala.

Importante

La Plataforma universal de Windows no representa correctamente texto anisotrópico escalado.

El escalado anisotrópico hace que el ancho del trazo sea diferente para las líneas alineadas con los ejes horizontales y verticales. (Esto también es evidente en la primera imagen de esta página). Si no desea que el ancho del trazo se vea afectado por los factores de escalado, establézcalo en 0 y siempre será un píxel ancho independientemente de la configuración de Scale.

El escalado es relativo a la esquina superior izquierda del lienzo. Puede que sea exactamente lo que quiere, pero puede que no. Supongamos que desea colocar el texto y el rectángulo en otro lugar del lienzo y desea escalarlo en relación con su centro. En ese caso, puede usar la cuarta versión del método Scale, que incluye dos parámetros adicionales para especificar el centro de escalado:

public void Scale (Single sx, Single sy, Single px, Single py)

Los parámetros px y py definen un punto que a veces se denomina centro de escalado, pero en la documentación de SkiaSharp se conoce como punto dinámico. Se trata de un punto relativo a la esquina superior izquierda del lienzo que no se ve afectado por el escalado. Todo el escalado se produce en relación con ese centro.

En la página Escala centrada se muestra cómo funciona. El controlador PaintSurface es similar al programa de escala básica, salvo que el valor margin se calcula para centrar el texto horizontalmente, lo que implica que el programa funciona mejor en modo vertical:

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

    canvas.Clear(SKColors.SkyBlue);

    using (SKPaint strokePaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Red,
        StrokeWidth = 3,
        PathEffect = SKPathEffect.CreateDash(new float[] { 7, 7 }, 0)
    })
    using (SKPaint textPaint = new SKPaint
    {
        Style = SKPaintStyle.Fill,
        Color = SKColors.Blue,
        TextSize = 50
    })
    {
        SKRect textBounds = new SKRect();
        textPaint.MeasureText(Title, ref textBounds);
        float margin = (info.Width - textBounds.Width) / 2;

        float sx = (float)xScaleSlider.Value;
        float sy = (float)yScaleSlider.Value;
        float px = margin + textBounds.Width / 2;
        float py = margin + textBounds.Height / 2;

        canvas.Scale(sx, sy, px, py);

        SKRect borderRect = SKRect.Create(new SKPoint(margin, margin), textBounds.Size);
        canvas.DrawRoundRect(borderRect, 20, 20, strokePaint);
        canvas.DrawText(Title, margin, -textBounds.Top + margin, textPaint);
    }
}

La esquina superior izquierda del rectángulo redondeado se coloca a margin píxeles de la izquierda del lienzo y a margin píxeles de la parte superior. Los dos últimos argumentos para el método Scale se establecen en esos valores más el ancho y alto del texto, que también es el ancho y alto del rectángulo redondeado. Esto significa que todo el escalado es relativo al centro de ese rectángulo:

Captura de pantalla triple de la página Escala centrada

Los elementos Slider de este programa tienen un intervalo de –10 a 10. Como puede ver, los valores negativos de escalado vertical (como en la pantalla Android del centro) hacen que los objetos se volteen alrededor del eje horizontal que pasa por el centro del escalado. Los valores negativos de escalado horizontal (como en la pantalla de UWP a la derecha) hacen que los objetos se volteen alrededor del eje vertical que pasa por el centro del escalado.

La versión del método Scale con puntos dinámicos es un acceso directo para una serie de tres llamadas Translate y Scale. Es posible que desee ver cómo funciona esto reemplazando el método Scale en la página Escala centrada por lo siguiente:

canvas.Translate(-px, -py);

Estos son los negativos de las coordenadas del punto dinámico.

Ejecute el programa otra vez. Verá que el rectángulo y el texto se desplazan para que el centro esté en la esquina superior izquierda del lienzo. Apenas se ve. Los controles deslizantes no funcionan porque ahora el programa no se escala en absoluto.

Ahora agregue la llamada básica Scale (sin un centro de escalado) antes de esa llamada Translate:

canvas.Scale(sx, sy);
canvas.Translate(–px, –py);

Si está familiarizado con este ejercicio en otros sistemas de programación de gráficos, podría pensar que es incorrecto, pero no lo es. Skia controla las llamadas de transformación sucesivas de forma un poco diferente a lo que usted podría estar acostumbrado.

Con las llamadas sucesivas Scale y Translate, el centro del rectángulo redondeado sigue en la esquina superior izquierda, pero ahora puede escalarlo en relación con la esquina superior izquierda del lienzo, que también es el centro del rectángulo redondeado.

Ahora, antes de esa llamada Scale, agregue otra llamada Translate con los valores de centro:

canvas.Translate(px, py);
canvas.Scale(sx, sy);
canvas.Translate(–px, –py);

Esto mueve el resultado escalado a la posición original. Estas tres llamadas son equivalentes a:

canvas.Scale(sx, sy, px, py);

Las transformaciones individuales se componen para que la fórmula de transformación total sea:

x' = sx · (x – px) + px

y' = sy · (y – py) + py

Tenga en cuenta que los valores predeterminados de sx y sy son 1. Es fácil convencerse de que estas fórmulas no transforman el punto dinámico (px, py). Permanece en la misma ubicación relativa al lienzo.

Al combinar llamadas Translate y Scale, el orden es importante. Si la Translate viene después de la Scale, los factores de traslación se escalan eficazmente mediante los factores de escalado. Si la Translate viene antes de la Scale, los factores de traslación no se escalan. Este proceso se vuelve algo más claro (aunque más matemático) cuando se introduce el asunto de las matrices de transformación.

La clase SKPath define una propiedad Bounds de solo lectura que devuelve un SKRect que define la extensión de las coordenadas en la ruta de acceso. Por ejemplo, cuando la propiedad Bounds se obtiene de la ruta de acceso del hendecagrama creado anteriormente, las propiedades Left y Top del rectángulo son aproximadamente –100, las propiedades Right y Bottom son aproximadamente 100, y las propiedades Width y Height son aproximadamente 200. (La mayoría de los valores reales son un poco menos porque los puntos de las estrellas se definen mediante un círculo con un radio de 100, pero solo el punto superior es paralelo con los ejes horizontales o verticales).

La disponibilidad de esta información implica que debe ser posible derivar factores de escala y traslación adecuados para escalar una ruta de acceso al tamaño del lienzo. La página Escalado anisotrópico muestra esto con la estrella de 11 puntas. Una escala anisotrópica significa que es desigual en las direcciones horizontales y verticales, lo que significa que la estrella no conservará su relación de aspecto original. Este es el código pertinente en el controlador PaintSurface:

SKPath path = HendecagramPage.HendecagramPath;
SKRect pathBounds = path.Bounds;

using (SKPaint fillPaint = new SKPaint
{
    Style = SKPaintStyle.Fill,
    Color = SKColors.Pink
})
using (SKPaint strokePaint = new SKPaint
{
    Style = SKPaintStyle.Stroke,
    Color = SKColors.Blue,
    StrokeWidth = 3,
    StrokeJoin = SKStrokeJoin.Round
})
{
    canvas.Scale(info.Width / pathBounds.Width,
                 info.Height / pathBounds.Height);
    canvas.Translate(-pathBounds.Left, -pathBounds.Top);

    canvas.DrawPath(path, fillPaint);
    canvas.DrawPath(path, strokePaint);
}

El rectángulo pathBounds se obtiene cerca de la parte superior de este código y, a continuación, se usa más adelante con el ancho y alto del lienzo en la llamada Scale. Esa llamada por sí misma escalará las coordenadas de la ruta de acceso cuando se represente mediante la llamada DrawPath, pero la estrella se centrará en la esquina superior derecha del lienzo. Debe desplazarse hacia abajo y hacia la izquierda. Este es el trabajo de la llamada Translate. Esas dos propiedades de pathBounds son aproximadamente 100, por lo que los factores de traslación son aproximadamente –100. Dado que la llamada Translate es después de la llamada Scale, esos valores se escalan eficazmente mediante los factores de escalado, por lo que mueven el centro de la estrella al centro del lienzo:

Captura de pantalla triple de la página Escalado anisotrópico

Otra manera de pensar en las llamadas Scale y Translate consiste en determinar el efecto en la secuencia inversa: la llamada Translate desplaza la ruta de acceso para que sea totalmente visible pero orientada en la esquina superior izquierda del lienzo. A continuación, el método Scale hace que esa estrella sea mayor en relación con la esquina superior izquierda.

En realidad, parece que la estrella es un poco más grande que el lienzo. El problema es el ancho del trazo. La propiedad Bounds de SKPath indica las dimensiones de las coordenadas codificadas en la ruta de acceso y eso es lo que usa el programa para escalarla. Cuando la ruta de acceso se representa con un ancho de trazo determinado, la ruta de acceso representada es mayor que el lienzo.

Para corregir este problema, debe compensarlo. Un enfoque sencillo en este programa es agregar la siguiente instrucción justo antes de la llamada Scale:

pathBounds.Inflate(strokePaint.StrokeWidth / 2,
                   strokePaint.StrokeWidth / 2);

Esto aumenta el rectángulo pathBounds en 1,5 unidades en los cuatro lados. Se trata de una solución razonable solo cuando se redondea la combinación de trazo. Una unión a inglete puede ser más larga y es difícil de calcular.

También puede usar una técnica similar con texto, como se muestra en la página Texto anisotrópico. Esta es la parte pertinente del controlador PaintSurface de la clase AnisotropicTextPage:

using (SKPaint textPaint = new SKPaint
{
    Style = SKPaintStyle.Stroke,
    Color = SKColors.Blue,
    StrokeWidth = 0.1f,
    StrokeJoin = SKStrokeJoin.Round
})
{
    SKRect textBounds = new SKRect();
    textPaint.MeasureText("HELLO", ref textBounds);

    // Inflate bounds by the stroke width
    textBounds.Inflate(textPaint.StrokeWidth / 2,
                       textPaint.StrokeWidth / 2);

    canvas.Scale(info.Width / textBounds.Width,
                 info.Height / textBounds.Height);
    canvas.Translate(-textBounds.Left, -textBounds.Top);

    canvas.DrawText("HELLO", 0, 0, textPaint);
}

Es una lógica similar y el texto se expande al tamaño de la página en función del rectángulo de límites de texto devuelto de MeasureText (que es un poco mayor que el texto real):

Captura de pantalla triple de la página Prueba anisotrópica

Si necesita conservar la relación de aspecto de los objetos gráficos, querrá usar el escalado isotrópico. En la página Escalado isotrópico se muestra esto para la estrella de 11 puntas. Conceptualmente, los pasos para mostrar un objeto gráfico en el centro de la página con escala isotrópico son:

  • Traslade el centro del objeto gráfico a la esquina superior izquierda.
  • Escale el objeto en función del mínimo de las dimensiones de página horizontal y vertical divididas por las dimensiones gráficas del objeto.
  • Traslade el centro del objeto escalado al centro de la página.

El IsotropicScalingPage realiza estos pasos en orden inverso antes de mostrar la estrella:

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

    canvas.Clear();

    SKPath path = HendecagramArrayPage.HendecagramPath;
    SKRect pathBounds = path.Bounds;

    using (SKPaint fillPaint = new SKPaint())
    {
        fillPaint.Style = SKPaintStyle.Fill;

        float scale = Math.Min(info.Width / pathBounds.Width,
                               info.Height / pathBounds.Height);

        for (int i = 0; i <= 10; i++)
        {
            fillPaint.Color = new SKColor((byte)(255 * (10 - i) / 10),
                                          0,
                                          (byte)(255 * i / 10));
            canvas.Save();
            canvas.Translate(info.Width / 2, info.Height / 2);
            canvas.Scale(scale);
            canvas.Translate(-pathBounds.MidX, -pathBounds.MidY);
            canvas.DrawPath(path, fillPaint);
            canvas.Restore();

            scale *= 0.9f;
        }
    }
}

El código también muestra la estrella 10 veces más, cada vez que disminuye el factor de escala en un 10 % y cambia progresivamente el color de rojo a azul:

Captura de pantalla triple de la página Escalado isotrópico