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