Píxeles y unidades independientes del dispositivo
Diferencias entre las coordenadas de SkiaSharp y las de Xamarin.Forms
En este artículo se exploran las diferencias en el sistema de coordenadas usado en SkiaSharp y Xamarin.Forms. Puede obtener información para convertir entre los dos sistemas de coordenadas y también dibujar gráficos que rellenen un área determinada:
Si lleva un tiempo programando en Xamarin.Forms, puede que tenga una idea de las coordenadas y los tamaños de Xamarin.Forms. Los círculos dibujados en los dos artículos anteriores pueden parecerle un poco pequeños.
Esos círculos son pequeños en comparación con los tamaños Xamarin.Forms. De forma predeterminada, SkiaSharp dibuja en unidades de píxeles, mientras que Xamarin.Forms basa las coordenadas y los tamaños en una unidad independiente del dispositivo establecida por la plataforma subyacente. (Puede encontrar más información sobre el sistema de coordenadas de Xamarin.Forms en el Capítulo 5. Lidiando con tamaños del libro Creación de aplicaciones móviles con Xamarin.Forms).
La página del programa de ejemplo titulada Tamaño de superficie usa la salida de texto de SkiaSharp para mostrar el tamaño de la superficie de visualización de tres orígenes diferentes:
- Las propiedades Xamarin.Forms
Width
yHeight
normales del objetoSKCanvasView
. - La propiedad
CanvasSize
del objetoSKCanvasView
. - La propiedad
Size
del valorSKImageInfo
, que es coherente con las propiedadesWidth
yHeight
usadas en las dos páginas anteriores.
La clase SurfaceSizePage
muestra cómo mostrar estos valores. El constructor guarda el objeto SKCanvasView
como un campo, por lo que se puede acceder a él en el controlador de eventos PaintSurface
:
SKCanvasView canvasView;
public SurfaceSizePage()
{
Title = "Surface Size";
canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
SKCanvas
incluye seis métodos DrawText
diferentes, pero este método DrawText
es el más sencillo:
public void DrawText (String text, Single x, Single y, SKPaint paint)
Especifique la cadena de texto, las coordenadas X e Y donde se va a comenzar el texto y un objeto SKPaint
. La coordenada X especifica dónde se sitúa el lado izquierdo del texto, pero cuidado: la coordenada Y especifica la posición de la línea de base del texto. Si alguna vez ha escrito a mano en papel rayado, la línea de base es la línea sobre la que se asientan los caracteres y por debajo de la cual descienden los descendentes (como los de las letras g, p, q e y).
El objeto SKPaint
permite especificar el color del texto, la familia de fuentes y el tamaño del texto. De forma predeterminada, la propiedad TextSize
tiene un valor de 12, lo que da como resultado texto diminuto en dispositivos de alta resolución, como teléfonos. Salvo en las aplicaciones más sencillas, también necesitará información sobre el tamaño del texto que va a mostrar. La clase SKPaint
define una propiedad FontMetrics
y varios métodos MeasureText
, pero para necesidades menos sofisticadas, la propiedad FontSpacing
proporciona un valor recomendado para espaciar líneas sucesivas de texto.
El siguiente controlador PaintSurface
crea un objeto SKPaint
para un TextSize
de 40 píxeles, que es el alto vertical deseado del texto desde la parte superior de los ascendentes hasta la parte inferior de los descendientes. El valor FontSpacing
que devuelve el objeto SKPaint
es un poco mayor que ese, unos 47 píxeles.
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
SKPaint paint = new SKPaint
{
Color = SKColors.Black,
TextSize = 40
};
float fontSpacing = paint.FontSpacing;
float x = 20; // left margin
float y = fontSpacing; // first baseline
float indent = 100;
canvas.DrawText("SKCanvasView Height and Width:", x, y, paint);
y += fontSpacing;
canvas.DrawText(String.Format("{0:F2} x {1:F2}",
canvasView.Width, canvasView.Height),
x + indent, y, paint);
y += fontSpacing * 2;
canvas.DrawText("SKCanvasView CanvasSize:", x, y, paint);
y += fontSpacing;
canvas.DrawText(canvasView.CanvasSize.ToString(), x + indent, y, paint);
y += fontSpacing * 2;
canvas.DrawText("SKImageInfo Size:", x, y, paint);
y += fontSpacing;
canvas.DrawText(info.Size.ToString(), x + indent, y, paint);
}
El método comienza la primera línea de texto con una coordenada X de 20 (para un poco de margen a la izquierda) y una coordenada Y de fontSpacing
, que es un poco más de lo necesario para mostrar el alto completo de la primera línea de texto en la parte superior de la superficie de presentación. Después de cada llamada a DrawText
, la coordenada Y aumenta en uno o dos incrementos de fontSpacing
.
Esta es la ejecución del programa:
Como puede ver, la propiedad CanvasSize
del SKCanvasView
y la propiedad Size
del valor SKImageInfo
son coherentes en la generación de informes de las dimensiones de píxel. Las propiedades Height
y Width
del SKCanvasView
son propiedades Xamarin.Forms e informan del tamaño de la vista en las unidades independientes del dispositivo definidas por la plataforma.
El simulador de iOS siete de la izquierda tiene dos píxeles por unidad independiente del dispositivo, y el Android Nexus 5 del centro tiene tres píxeles por unidad. Por eso, el círculo simple mostrado anteriormente tiene diferentes tamaños en distintas plataformas.
Si prefiere trabajar completamente en unidades independientes del dispositivo, puede hacerlo estableciendo la propiedad IgnorePixelScaling
del SKCanvasView
en true
. Sin embargo, es posible que no le gusten los resultados. SkiaSharp representa los gráficos en una superficie de dispositivo más pequeña, con un tamaño de píxel igual al tamaño de la vista en unidades independientes del dispositivo. (Por ejemplo, SkiaSharp usaría una superficie de visualización de 360 x 512 píxeles en el Nexus 5). A continuación, aumenta el tamaño de la imagen, lo que produce dentelladas perceptibles en el mapa de bits.
Para mantener la misma resolución de imágenes, una solución mejor es escribir sus propias funciones sencillas para convertir entre los dos sistemas de coordenadas.
Además del método DrawCircle
, SKCanvas
también define dos métodos DrawOval
que dibujan una elipse. Una elipse se define mediante dos radios en lugar de un solo radio. Estos se conocen como el radio principal y el radio menor. El método DrawOval
dibuja una elipse con los dos radios paralelos a los ejes X e Y. (Si necesita dibujar una elipse con ejes que no son paralelos a los ejes X e Y, puede usar una transformación de rotación como se describe en el artículo La transformación de rotación o una ruta de acceso gráfica, como se describe en el artículo Tres maneras de dibujar un arco). Esta sobrecarga del método DrawOval
asigna nombres a los dos parámetros radios rx
y ry
para indicar que son paralelos a los ejes X e Y:
public void DrawOval (Single cx, Single cy, Single rx, Single ry, SKPaint paint)
¿Es posible dibujar una elipse que rellene la superficie de visualización? La página relleno de elipse muestra cómo. El controlador de eventos PaintSurface
de la clase EllipseFillPage.xaml.cs resta la mitad del ancho del trazo del los valores xRadius
y yRadius
para ajustarse a toda la elipse y su contorno dentro de la superficie de presentación:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
float strokeWidth = 50;
float xRadius = (info.Width - strokeWidth) / 2;
float yRadius = (info.Height - strokeWidth) / 2;
SKPaint paint = new SKPaint
{
Style = SKPaintStyle.Stroke,
Color = SKColors.Blue,
StrokeWidth = strokeWidth
};
canvas.DrawOval(info.Width / 2, info.Height / 2, xRadius, yRadius, paint);
}
Aquí se está ejecutando:
El otro método DrawOval
tiene un argumento SKRect
, que es un rectángulo definido en términos de las coordenadas X e Y de su esquina superior izquierda y esquina inferior derecha. El óvalo rellena ese rectángulo, lo que sugiere que podría ser posible usarlo en la página Relleno de elipse de esta manera:
SKRect rect = new SKRect(0, 0, info.Width, info.Height);
canvas.DrawOval(rect, paint);
Sin embargo, esto trunca todos los bordes del contorno de la elipse en los cuatro lados. Debe ajustar todos los argumentos del constructor SKRect
en función del strokeWidth
para que esto funcione correctamente:
SKRect rect = new SKRect(strokeWidth / 2,
strokeWidth / 2,
info.Width - strokeWidth / 2,
info.Height - strokeWidth / 2);
canvas.DrawOval(rect, paint);