Visualización segmentada de mapas de bits de SkiaSharp

Download SampleDescargar el ejemplo

El objeto SkiaSharp SKCanvas define un método denominado DrawBitmapNinePatch y dos métodos denominados DrawBitmapLattice que son muy similares. Ambos métodos representan un mapa de bits al tamaño de un rectángulo de destino, pero en lugar de ajustar el mapa de bits uniformemente, muestran partes del mapa de bits en sus dimensiones de píxeles y ajustan otras partes del mapa de bits para que se ajuste al rectángulo:

Segmented Samples

Estos métodos se suelen usar para representar mapas de bits que forman parte de objetos de interfaz de usuario, como botones. Al diseñar un botón, por lo general quiere que el tamaño de un botón se base en el contenido del botón, pero probablemente quiera que el borde del botón sea el mismo ancho independientemente del contenido del botón. Es una aplicación ideal de DrawBitmapNinePatch.

DrawBitmapNinePatch es un caso especial de DrawBitmapLattice, pero es más fácil de usar y comprender los dos métodos.

Pantalla de nueve revisiones

Conceptualmente, DrawBitmapNinePatch divide un mapa de bits en nueve rectángulos:

Nine Patch

Los rectángulos de las cuatro esquinas se muestran en sus tamaños de píxel. Como indican las flechas, las demás áreas del mapa de bits se extienden horizontal o verticalmente al área del rectángulo de destino. El rectángulo del centro se extiende tanto horizontal como verticalmente.

Si no hay suficiente espacio en el rectángulo de destino para mostrar incluso las cuatro esquinas de sus dimensiones de píxeles, se reducen verticalmente al tamaño disponible y no se muestran las cuatro esquinas.

Para dividir un mapa de bits en estos nueve rectángulos, solo es necesario especificar el rectángulo en el centro. Esta es la sintaxis del método DrawBitmapNinePatch:

canvas.DrawBitmapNinePatch(bitmap, centerRectangle, destRectangle, paint);

El rectángulo central es relativo al mapa de bits. Es un valor SKRectI (la versión entera de SKRect) y todas las coordenadas y tamaños están en unidades de píxeles. El rectángulo de destino es relativo a la superficie de visualización. El argumento paint es opcional.

La página de Visualización de nueve revisiones en el ejemplo SkiaSharpFormsDemos usa primero un constructor estático para crear una propiedad estática pública de tipo SKBitmap:

public partial class NinePatchDisplayPage : ContentPage
{
    static NinePatchDisplayPage()
    {
        using (SKCanvas canvas = new SKCanvas(FiveByFiveBitmap))
        using (SKPaint paint = new SKPaint
        {
            Style = SKPaintStyle.Stroke,
            Color = SKColors.Red,
            StrokeWidth = 10
        })
        {
            for (int x = 50; x < 500; x += 100)
                for (int y = 50; y < 500; y += 100)
                {
                    canvas.DrawCircle(x, y, 40, paint);
                }
        }
    }

    public static SKBitmap FiveByFiveBitmap { get; } = new SKBitmap(500, 500);
    ···
}

Otras dos páginas de este artículo usan ese mismo mapa de bits. El mapa de bits es de 500 píxeles cuadrados y consta de una matriz de 25 círculos, todo el mismo tamaño, cada uno ocupa un área cuadrada de 100 píxeles:

Circle Grid

El constructor de instancia del programa crea un SKCanvasView con un controlador PaintSurface que usa DrawBitmapNinePatch para mostrar el mapa de bits ajustado a toda su superficie de visualización:

public class NinePatchDisplayPage : ContentPage
{
    ···
    public NinePatchDisplayPage()
    {
        Title = "Nine-Patch Display";

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

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

        canvas.Clear();

        SKRectI centerRect = new SKRectI(100, 100, 400, 400);
        canvas.DrawBitmapNinePatch(FiveByFiveBitmap, centerRect, info.Rect);
    }
}

El rectángulo centerRect abarca la matriz central de 16 círculos. Los círculos de las esquinas se muestran en sus dimensiones de píxel y todo lo demás se ajusta en consecuencia:

Nine-Patch Display

La página para UWP tiene un ancho de 500 píxeles y, por tanto, muestra las filas superior e inferior como una serie de círculos del mismo tamaño. De lo contrario, todos los círculos que no están en las esquinas se ajustan para formar puntos suspensivos.

Para una visualización extraña de objetos consistentes en una combinación de círculos y elipses, pruebe a definir el rectángulo central de modo que se superponga a filas y columnas de círculos:

SKRectI centerRect = new SKRectI(150, 150, 350, 350);

Pantalla de cuadrícula

Los dos métodos DrawBitmapLattice son similares a DrawBitmapNinePatch, pero se generalizan para cualquier número de divisiones horizontales o verticales. Estas divisiones se definen mediante matrices de enteros correspondientes a píxeles.

El método DrawBitmapLattice con parámetros para estas matrices de enteros no parece funcionar. El método DrawBitmapLattice con un parámetro de tipo SKLattice funciona y es el que se usa en los ejemplos que se muestran a continuación.

La estructura SKLattice define cuatro propiedades:

  • XDivs, una matriz de enteros
  • YDivs, una matriz de enteros
  • Flags, una matriz de SKLatticeFlags, un tipo de enumeración
  • Bounds de tipo Nullable<SKRectI> para especificar un rectángulo de origen opcional dentro del mapa de bits

La matriz XDivs divide el ancho del mapa de bits en bandas verticales. La primera franja se extiende del píxel 0 de la izquierda a XDivs[0]. Esta franja se representa en su ancho de píxel. La segunda franja se extiende de XDivs[0] a XDivs[1] y se ajusta. La tercera franja se extiende de XDivs[1] a XDivs[2] y se representa en su ancho de píxel. La última franja se extiende desde el último elemento de la matriz hasta el borde derecho del mapa de bits. Si la matriz tiene un número par de elementos, se muestra en su ancho de píxel. De lo contrario, se ajusta. El número total de franjas verticales es uno más que el número de elementos de la matriz.

La matriz YDivs es similar. Divide el alto de la matriz en franjas horizontales.

Juntas, la matriz XDivs y YDivs dividen el mapa de bits en rectángulos. El número de rectángulos es igual al producto del número de franjas horizontales y del número de franjas verticales.

Según la documentación de Skia, la matriz Flags contiene un elemento para cada rectángulo, primero la fila superior de rectángulos, luego la segunda fila, etc. La matriz Flags es de tipo SKLatticeFlags, una enumeración con los miembros siguientes:

  • Default con el valor 0
  • Transparent con el valor 1

Sin embargo, estas marcas no parecen funcionar de la forma esperada y es mejor ignorarlas. Pero no establezca la propiedad Flags en null. Establézcala en una matriz de valores SKLatticeFlags lo suficientemente grande como para abarcar el número total de rectángulos.

La página Parche de nueve cuadrículas usa DrawBitmapLattice para imitar DrawBitmapNinePatch. Usa el mismo mapa de bits creado en NinePatchDisplayPage:

public class LatticeNinePatchPage : ContentPage
{
    SKBitmap bitmap = NinePatchDisplayPage.FiveByFiveBitmap;

    public LatticeNinePatchPage ()
    {
        Title = "Lattice Nine-Patch";

        SKCanvasView canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;
    }
    `
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        SKLattice lattice = new SKLattice();
        lattice.XDivs = new int[] { 100, 400 };
        lattice.YDivs = new int[] { 100, 400 };
        lattice.Flags = new SKLatticeFlags[9]; 

        canvas.DrawBitmapLattice(bitmap, lattice, info.Rect);
    }
}

Las propiedades XDivs y YDivs se establecen en matrices de solo dos enteros, dividiendo el mapa de bits en tres franjas tanto horizontal como verticalmente: del píxel 0 al píxel 100 (representado en el tamaño de píxel), del píxel 100 al píxel 400 (extendido) y del píxel 400 al píxel 500 (tamaño de píxel). Juntos, XDivs y YDivs definen un total de 9 rectángulos, que es el tamaño de la matriz de Flags. Simplemente crear la matriz es suficiente para crear una matriz de valores SKLatticeFlags.Default.

La pantalla es idéntica al programa anterior:

Lattice Nine-Patch

La página Pantalla de cuadrícula divide el mapa de bits en 16 rectángulos:

public class LatticeDisplayPage : ContentPage
{
    SKBitmap bitmap = NinePatchDisplayPage.FiveByFiveBitmap;

    public LatticeDisplayPage()
    {
        Title = "Lattice Display";

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

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

        canvas.Clear();

        SKLattice lattice = new SKLattice();
        lattice.XDivs = new int[] { 100, 200, 400 };
        lattice.YDivs = new int[] { 100, 300, 400 };

        int count = (lattice.XDivs.Length + 1) * (lattice.YDivs.Length + 1);
        lattice.Flags = new SKLatticeFlags[count];

        canvas.DrawBitmapLattice(bitmap, lattice, info.Rect);
    }
}

Las matrices XDivs y YDivs son algo diferentes, lo que hace que la pantalla no sea tan simétrica como en los ejemplos anteriores:

Lattice Display

En las imágenes de iOS y Android de la izquierda, solo los círculos más pequeños se representan en sus tamaños de píxel. Todo lo demás está ajustado.

La página Pantalla de cuadrícula generaliza la creación de la matriz de Flags, lo que le permite experimentar con XDivs y YDivs más fácilmente. En concreto, querrá ver lo que sucede al establecer el primer elemento de la matriz de XDivs o YDivs en 0.