Compartir a través de


Conceptos básicos de mapas de bits en SkiaSharp

Carga de mapas de bits de distintos orígenes y visualización de los mismos.

La compatibilidad con mapas de bits en SkiaSharp es bastante extensa. En este artículo solo se tratan los aspectos básicos: cómo cargar mapas de bits y cómo mostrarlos.

Visualización de dos mapas de bits

En la sección Mapas de bits de SkiaSharp encontrará una descripción mucho más detallada de los mapas de bits.

Un mapa de bits de SkiaSharp es un objeto de tipo SKBitmap. Hay muchas maneras de crear un mapa de bits, pero en este artículo solo se trata el método SKBitmap.Decode, que carga el mapa de bits desde un objeto Stream de .NET.

En la página Mapas de bits básicos del programa SkiaSharpFormsDemos se muestra cómo cargar mapas de bits de tres orígenes diferentes:

  • De Internet
  • Desde un recurso incrustado en el archivo ejecutable
  • Desde la biblioteca de fotos del usuario

Se definen tres objetos SKBitmap como campos para estas tres fuentes en la clase BasicBitmapsPage:

public class BasicBitmapsPage : ContentPage
{
    SKCanvasView canvasView;
    SKBitmap webBitmap;
    SKBitmap resourceBitmap;
    SKBitmap libraryBitmap;

    public BasicBitmapsPage()
    {
        Title = "Basic Bitmaps";

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

Carga de un mapa de bits desde la Web

Para cargar un mapa de bits basado en una dirección URL, puede usar la clase HttpClient. Hay que crear una sola instancia de HttpClient y reutilizarla, por lo que se debe almacenar como un campo:

HttpClient httpClient = new HttpClient();

Al usar HttpClient con aplicaciones iOS y Android, es recomendable establecer las propiedades del proyecto como se describe en los documentos de Seguridad de la capa de transporte (TLS) 1.2.

Dado que es más conveniente usar el operador await con HttpClient, el código no se puede ejecutar en el constructor BasicBitmapsPage. En lugar de eso, forma parte de la invalidación OnAppearing. La dirección URL aquí apunta a un área del sitio web de Xamarin con algunos mapas de bits de ejemplo. Un paquete en el sitio web permite anexar una especificación para cambiar el tamaño del mapa de bits a un ancho determinado:

protected override async void OnAppearing()
{
    base.OnAppearing();

    // Load web bitmap.
    string url = "https://developer.xamarin.com/demo/IMG_3256.JPG?width=480";

    try
    {
        using (Stream stream = await httpClient.GetStreamAsync(url))
        using (MemoryStream memStream = new MemoryStream())
        {
            await stream.CopyToAsync(memStream);
            memStream.Seek(0, SeekOrigin.Begin);

            webBitmap = SKBitmap.Decode(memStream);
            canvasView.InvalidateSurface();
        };
    }
    catch
    {
    }
}

El sistema operativo Android genera una excepción al usar el Stream devuelto por GetStreamAsync en el método SKBitmap.Decode, ya que realiza una operación larga en un subproceso principal. Por este motivo, el contenido del archivo de mapa de bits se copia en un objeto MemoryStream mediante CopyToAsync.

El método estático SKBitmap.Decode se encarga de descodificar archivos de mapa de bits. Funciona con formatos de mapa de bits JPEG, PNG y GIF y almacena los resultados en un formato de SkiaSharp interno. En este momento, se debe invalidar la SKCanvasView para permitir que el controlador PaintSurface actualice la pantalla.

Carga de un recurso de mapa de bits

En términos de código, el enfoque más sencillo para cargar mapas de bits es incluir un recurso de mapa de bits directamente en la aplicación. El programa SkiaSharpFormsDemos tiene una carpeta denominada Media que contiene varios archivos de mapa de bits, incluido uno denominado monkey.png. En el caso de los mapas de bits almacenados como recursos de programa, se debe usar el cuadro de diálogo Propiedades para proporcionar al archivo una acción de compilación de recurso incrustado.

Cada recurso incrustado tiene un id. de recurso que consta del nombre del proyecto, la carpeta y el nombre de archivo, todos conectados por puntos: SkiaSharpFormsDemos.Media.monkey.png. Para obtener acceso a este recurso, especifique ese id. de recurso como argumento para el método GetManifestResourceStream de la clase Assembly:

string resourceID = "SkiaSharpFormsDemos.Media.monkey.png";
Assembly assembly = GetType().GetTypeInfo().Assembly;

using (Stream stream = assembly.GetManifestResourceStream(resourceID))
{
    resourceBitmap = SKBitmap.Decode(stream);
}

Este objeto Stream se puede pasar directamente al método SKBitmap.Decode.

Carga de un mapa de bits desde la biblioteca de fotos

El usuario también puede cargar una foto desde la biblioteca de imágenes del dispositivo. Esta funcionalidad no la proporciona Xamarin.Forms. El trabajo requiere un servicio de dependencia, como el que se describe en el artículo Selección de una foto de la biblioteca de imágenes.

El archivo IPhotoLibrary.cs del proyecto SkiaSharpFormsDemos y los tres archivos PhotoLibrary.cs de los proyectos de plataforma se han adaptado de ese artículo. Además, se ha modificado el archivo Android MainActivity.cs como se describe en el artículo y se ha concedido permiso al proyecto de iOS para acceder a la biblioteca de fotos con dos líneas en la parte inferior del archivo info.plist.

El constructor BasicBitmapsPage agrega un objeto TapGestureRecognizer a la SKCanvasView para recibir notificaciones de pulsaciones. Al recibir una pulsación, el controlador Tapped obtiene acceso al servicio de dependencias del selector de imágenes y llama a PickPhotoAsync. Si se devuelve un objeto Stream, se pasa al método SKBitmap.Decode:

// Add tap gesture recognizer
TapGestureRecognizer tapRecognizer = new TapGestureRecognizer();
tapRecognizer.Tapped += async (sender, args) =>
{
    // Load bitmap from photo library
    IPhotoLibrary photoLibrary = DependencyService.Get<IPhotoLibrary>();

    using (Stream stream = await photoLibrary.PickPhotoAsync())
    {
        if (stream != null)
        {
            libraryBitmap = SKBitmap.Decode(stream);
            canvasView.InvalidateSurface();
        }
    }
};
canvasView.GestureRecognizers.Add(tapRecognizer);

Observe que el controlador Tapped también llama al método InvalidateSurface del objeto SKCanvasView. Esto genera una nueva llamada al controlador PaintSurface.

Visualización del mapa de bits

El controlador PaintSurface debe mostrar tres mapas de bits. El controlador supone que el teléfono está en modo vertical y divide el lienzo verticalmente en tres partes iguales.

El primer mapa de bits se muestra con el método DrawBitmap más sencillo. Todo lo que debe especificar son las coordenadas X e Y donde se colocará la esquina superior izquierda del mapa de bits:

public void DrawBitmap (SKBitmap bitmap, Single x, Single y, SKPaint paint = null)

Aunque hay un parámetro SKPaint definido, tiene un valor predeterminado de null y puede omitirlo. Los píxeles del mapa de bits se transfieren sin más a los píxeles de la superficie de visualización con una asignación uno a uno. Verá una aplicación de este argumento SKPaint en la sección Transparencia de SkiaSharp siguiente.

Un programa puede obtener las dimensiones de píxel de un mapa de bits con las propiedades Width y Height. Estas propiedades permiten al programa calcular las coordenadas para colocar el mapa de bits en el centro del tercio superior del lienzo:

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

    canvas.Clear();

    if (webBitmap != null)
    {
        float x = (info.Width - webBitmap.Width) / 2;
        float y = (info.Height / 3 - webBitmap.Height) / 2;
        canvas.DrawBitmap(webBitmap, x, y);
    }
    ...
}

Los otros dos mapas de bits se muestran con una versión de DrawBitmap con un parámetro SKRect:

public void DrawBitmap (SKBitmap bitmap, SKRect dest, SKPaint paint = null)

Una tercera versión de DrawBitmap tiene dos argumentos SKRect para especificar un subconjunto rectangular del mapa de bits que se va a mostrar, pero en este artículo no se usa esa versión.

Este es el código para mostrar el mapa de bits cargado desde un mapa de bits de recurso incrustado:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    ...
    if (resourceBitmap != null)
    {
        canvas.DrawBitmap(resourceBitmap,
            new SKRect(0, info.Height / 3, info.Width, 2 * info.Height / 3));
    }
    ...
}

El mapa de bits se ajusta a las dimensiones del rectángulo, por lo que el mono aparece estirado horizontalmente en estas capturas de pantalla:

Captura de pantalla triple de la página Mapas de bits básicos

La tercera imagen, que solo se puede ver al ejecutar el programa y cargar una foto de la biblioteca de imágenes propia, también se muestra dentro de un rectángulo, pero la posición y el tamaño del rectángulo se ajustan para mantener la relación de aspecto del mapa de bits. Este cálculo es un poco más complicado porque requiere calcular un factor de escala basado en el tamaño del mapa de bits y el rectángulo de destino, además de centrar el rectángulo en esa área:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    ...
    if (libraryBitmap != null)
    {
        float scale = Math.Min((float)info.Width / libraryBitmap.Width,
                               info.Height / 3f / libraryBitmap.Height);

        float left = (info.Width - scale * libraryBitmap.Width) / 2;
        float top = (info.Height / 3 - scale * libraryBitmap.Height) / 2;
        float right = left + scale * libraryBitmap.Width;
        float bottom = top + scale * libraryBitmap.Height;
        SKRect rect = new SKRect(left, top, right, bottom);
        rect.Offset(0, 2 * info.Height / 3);

        canvas.DrawBitmap(libraryBitmap, rect);
    }
    else
    {
        using (SKPaint paint = new SKPaint())
        {
            paint.Color = SKColors.Blue;
            paint.TextAlign = SKTextAlign.Center;
            paint.TextSize = 48;

            canvas.DrawText("Tap to load bitmap",
                info.Width / 2, 5 * info.Height / 6, paint);
        }
    }
}

Si aún no se ha cargado ningún mapa de bits desde la biblioteca de imágenes, el bloque else muestra texto para indicar al usuario que pulse la pantalla.

Puede mostrar mapas de bits con varios grados de transparencia, lo que se describe en el siguiente artículo: Transparencia de SkiaSharp.