Visualización de mapas de bits de SkiaSharp

Download SampleDescargar el ejemplo

El tema de los mapas de bits de SkiaSharp se introdujo en el artículo Conceptos básicos de los mapas de bits en SkiaSharp. En este artículo se muestran tres maneras de cargar mapas de bits y tres maneras de mostrar mapas de bits. En este artículo se revisan las técnicas para cargar mapas de bits y se profundiza en el uso de los métodos DrawBitmap de SKCanvas.

Displaying Sample

Los métodos DrawBitmapLattice y DrawBitmapNinePatch se describen en el artículo Visualización segmentada de mapas de bits de SkiaSharp.

Los ejemplos de esta página proceden de la aplicación SkiaSharpFormsDemos. En la página principal de esa aplicación, elija mapas de bits de SkiaSharp y, a continuación, vaya a la sección Mostrar mapas de bits.

Carga de un mapa de bits

Normalmente, un mapa de bits usado por una aplicación SkiaSharp procede de uno de los tres orígenes diferentes:

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

También es posible que una aplicación SkiaSharp cree un nuevo mapa de bits y, a continuación, dibuje en él o establezca el mapa de bits de forma algorítmica. Estas técnicas se describen en los artículos Crear y dibujar en mapas de bits de SkiaSharp y Acceso a píxeles de mapa de bits SkiaSharp.

En los tres ejemplos de código siguientes de carga de un mapa de bits, se supone que la clase contiene un campo de tipo SKBitmap:

SKBitmap bitmap;

Como se indica en el artículo Conceptos básicos de los mapas de bits en SkiaSharp, la mejor manera de cargar un mapa de bits a través de Internet es con la clase HttpClient. Una única instancia de la clase se puede definir 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.

El código que usa HttpClient a menudo implica el operador await, por lo que debe residir en un método async:

try
{
    using (Stream stream = await httpClient.GetStreamAsync("https:// ··· "))
    using (MemoryStream memStream = new MemoryStream())
    {
        await stream.CopyToAsync(memStream);
        memStream.Seek(0, SeekOrigin.Begin);

        bitmap = SKBitmap.Decode(memStream);
        ···
    };
}
catch
{
    ···
}

Observe que el objeto Stream obtenido de GetStreamAsync se copia en un MemoryStream. Android no permite que Stream de HttpClient se procese por el subproceso principal excepto en métodos asincrónicos.

El SKBitmap.Decode hace mucho trabajo: el objeto Stream pasado hace referencia a un bloque de memoria que contiene un mapa de bits completo en uno de los formatos de archivo de mapa de bits comunes, generalmente JPEG, PNG o GIF. El método Decode debe determinar el formato y, a continuación, descodificar el archivo de mapa de bits en el propio formato de mapa de bits interno de SkiaSharp.

Después de que el código llame a SKBitmap.Decode, probablemente invalidará el CanvasView para que el controlador PaintSurface pueda mostrar el mapa de bits recién cargado.

La segunda forma de cargar un mapa de bits es incluir el mapa de bits como un recurso incrustado en la biblioteca de .NET Standard a la que hacen referencia los proyectos de plataforma individuales. Se pasa un identificador de recurso al método GetManifestResourceStream. Este identificador de recurso consta del nombre de ensamblado, el nombre de carpeta y el nombre de archivo del recurso separados por puntos:

string resourceID = "assemblyName.folderName.fileName";
Assembly assembly = GetType().GetTypeInfo().Assembly;

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

Los archivos de mapa de bits también se pueden almacenar como recursos en el proyecto de plataforma individual para iOS, Android y la Plataforma universal de Windows (UWP). Sin embargo, cargar esos mapas de bits requiere código que se encuentra en el proyecto de plataforma.

Un tercer enfoque para obtener un mapa de bits procede de la biblioteca de imágenes del usuario. El código siguiente usa un servicio de dependencia que se incluye en la aplicación SkiaSharpFormsDemos. La biblioteca estándar de .NET SkiaSharpFormsDemo incluye la interfaz IPhotoLibrary, mientras que cada uno de los proyectos de plataforma contiene una clase PhotoLibrary que implementa esa interfaz.

IPhotoicturePicker picturePicker = DependencyService.Get<IPhotoLibrary>();

using (Stream stream = await picturePicker.GetImageStreamAsync())
{
    if (stream != null)
    {
        bitmap = SKBitmap.Decode(stream);
        ···
    }
}

Por lo general, este código también invalida el CanvasView para que el controlador PaintSurface pueda mostrar el nuevo mapa de bits.

La clase SKBitmap define varias propiedades útiles, incluidas Width y Height, que revelan las dimensiones de píxel del mapa de bits, así como muchos métodos, incluidos los métodos para crear mapas de bits, copiarlos y exponer los bits de píxel.

Mostrar en dimensiones de píxel

La clase Canvas de SkiaSharp define cuatro métodos DrawBitmap. Estos métodos permiten mostrar mapas de bits de dos maneras fundamentalmente diferentes:

  • Al especificar un valor SKPoint (o valores x y y independientes) se muestra el mapa de bits en sus dimensiones de píxeles. Los píxeles del mapa de bits se asignan directamente a píxeles de la pantalla de vídeo.
  • Especificar un rectángulo hace que el mapa de bits se extienda al tamaño y la forma del rectángulo.

Se muestra un mapa de bits en sus dimensiones de píxel mediante DrawBitmap con un parámetro SKPoint o DrawBitmap con parámetros x y y independientes:

DrawBitmap(SKBitmap bitmap, SKPoint pt, SKPaint paint = null)

DrawBitmap(SKBitmap bitmap, float x, float y, SKPaint paint = null)

Estos dos métodos son funcionalmente idénticos. El punto especificado indica la ubicación de la esquina superior izquierda del mapa de bits con respecto al lienzo. Dado que la resolución de píxeles de los dispositivos móviles es tan alta, los mapas de bits más pequeños suelen aparecer bastante pequeños en estos dispositivos.

El parámetro opcional SKPaint permite mostrar el mapa de bits mediante transparencia. Para ello, cree un objeto SKPaint y establezca la propiedad Color en cualquier valor SKColor con un canal alfa inferior a 1. Por ejemplo:

paint.Color = new SKColor(0, 0, 0, 0x80);

El 0x80 pasado como último argumento indica la transparencia del 50 %. También puede establecer un canal alfa en uno de los colores predefinidos:

paint.Color = SKColors.Red.WithAlpha(0x80);

Sin embargo, el propio color es irrelevante. Solo se examina el canal alfa cuando se usa el objeto SKPaint en una llamada DrawBitmap.

El objeto SKPaint también desempeña un papel al mostrar mapas de bits mediante modos de fusión o efectos de filtro. Estos se muestran en los artículos Modos de composición y de fusión de SkiaSharp y Filtros de imagen de SkiaSharp.

La página Dimensiones de píxeles del programa de ejemplo SkiaSharpFormsDemos muestra un recurso de mapa de bits de 320 píxeles de ancho por 240 píxeles de alto:

public class PixelDimensionsPage : ContentPage
{
    SKBitmap bitmap;

    public PixelDimensionsPage()
    {
        Title = "Pixel Dimensions";

        // Load the bitmap from a resource
        string resourceID = "SkiaSharpFormsDemos.Media.Banana.jpg";
        Assembly assembly = GetType().GetTypeInfo().Assembly;

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

        // Create the SKCanvasView and set the PaintSurface handler
        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();

        float x = (info.Width - bitmap.Width) / 2;
        float y = (info.Height - bitmap.Height) / 2;

        canvas.DrawBitmap(bitmap, x, y);
    }
}

El controlador PaintSurface centra el mapa de bits calculando los valores x y y en función de las dimensiones de píxel de la superficie de visualización y las dimensiones de píxel del mapa de bits:

Pixel Dimensions

Si la aplicación desea mostrar el mapa de bits en su esquina superior izquierda, simplemente pasaría coordenadas de (0, 0).

Un método para cargar mapas de bits de recursos

Muchos de los ejemplos que aparecerán necesitarán cargar recursos de mapa de bits. La clase estática BitmapExtensions de la solución SkiaSharpFormsDemos contiene un método para ayudar:

static class BitmapExtensions
{
    public static SKBitmap LoadBitmapResource(Type type, string resourceID)
    {
        Assembly assembly = type.GetTypeInfo().Assembly;

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

Observe el parámetro Type. Puede ser el objeto Type asociado a cualquier tipo del ensamblado que almacena el recurso de mapa de bits.

Este método LoadBitmapResource se usará en todos los ejemplos posteriores que requieren recursos de mapa de bits.

Estirar para rellenar un rectángulo

La clase SKCanvas también define un método DrawBitmap que representa el mapa de bits en un rectángulo y otro método DrawBitmap que representa un subconjunto rectangular del mapa de bits en un rectángulo:

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

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

En ambos casos, el mapa de bits se extiende para rellenar el rectángulo denominado dest. En el segundo método, el rectángulo source permite seleccionar un subconjunto del mapa de bits. El rectángulo dest es relativo al dispositivo de salida; el rectángulo source es relativo al mapa de bits.

La página Rellenar rectángulo muestra el primer de estos dos métodos mostrando el mismo mapa de bits usado en el ejemplo anterior en un rectángulo del mismo tamaño que el lienzo:

public class FillRectanglePage : ContentPage
{
    SKBitmap bitmap =
        BitmapExtensions.LoadBitmapResource(typeof(FillRectanglePage),
                                            "SkiaSharpFormsDemos.Media.Banana.jpg");
    public FillRectanglePage ()
    {
        Title = "Fill Rectangle";

        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();

        canvas.DrawBitmap(bitmap, info.Rect);
    }
}

Observe el uso del nuevo método BitmapExtensions.LoadBitmapResource para establecer el campo SKBitmap. El rectángulo de destino se obtiene de la propiedad Rect de SKImageInfo, que desribe el tamaño de la superficie de presentación:

Fill Rectangle

Esto no suele ser lo que se desea. La imagen se distorsiona al estirarse de forma diferente en las direcciones horizontal y vertical. Al mostrar un mapa de bits en algo distinto de su tamaño de píxel, normalmente quiere conservar la relación de aspecto original del mapa de bits.

Estirar y conservar la relación de aspecto

Estirar un mapa de bits al tiempo que conserva la relación de aspecto es un proceso también conocido como escalado uniforme. Ese término sugiere un enfoque algorítmico. Una posible solución se muestra en la página Escalado uniforme:

public class UniformScalingPage : ContentPage
{
    SKBitmap bitmap =
        BitmapExtensions.LoadBitmapResource(typeof(UniformScalingPage),
                                            "SkiaSharpFormsDemos.Media.Banana.jpg");
    public UniformScalingPage()
    {
        Title = "Uniform Scaling";

        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();

        float scale = Math.Min((float)info.Width / bitmap.Width, 
                               (float)info.Height / bitmap.Height);
        float x = (info.Width - scale * bitmap.Width) / 2;
        float y = (info.Height - scale * bitmap.Height) / 2;
        SKRect destRect = new SKRect(x, y, x + scale * bitmap.Width, 
                                           y + scale * bitmap.Height);

        canvas.DrawBitmap(bitmap, destRect);
    }
}

El controlador PaintSurface calcula un factor scale que es el mínimo de la relación del ancho y alto de la pantalla con respecto al ancho y alto del mapa de bits. Los valores x y y se pueden calcular para centrar el mapa de bits escalado dentro del ancho y alto de la pantalla. El rectángulo de destino tiene una esquina superior izquierda de x y y una esquina inferior derecha de esos valores más el ancho y alto escalados del mapa de bits:

Uniform Scaling

Gire el teléfono lateralmente para ver el mapa de bits extendido a esa área:

Uniform Scaling landscape

La ventaja de usar este factor scale resulta evidente cuando se quiere implementar un algoritmo ligeramente diferente. Supongamos que desea conservar la relación de aspecto del mapa de bits, pero también rellenar el rectángulo de destino. La única manera de hacerlo es recortando parte de la imagen, pero puede implementar ese algoritmo simplemente cambiando Math.Min a Math.Max en el código anterior. Este es el resultado:

Uniform Scaling alternative

La relación de aspecto del mapa de bits se conserva, pero se recortan las áreas de la izquierda y la derecha del mapa de bits.

Una función versátil de visualización de mapa de bits

Los entornos de programación basados en XAML (como UWP y Xamarin.Forms) tienen una facilidad para expandir o reducir el tamaño de los mapas de bits a la vez que conservan sus relaciones de aspecto. Aunque SkiaSharp no incluye esta característica, puede implementarla usted mismo. La clase BitmapExtensions incluida en la aplicación SkiaSharpFormsDemos muestra cómo. La clase define dos métodos DrawBitmap nuevos que realizan el cálculo de la relación de aspecto. Estos nuevos métodos son métodos de extensión de SKCanvas.

Los nuevos métodos DrawBitmap incluyen un parámetro de tipo BitmapStretch, una enumeración definida en el archivo BitmapExtensions.cs:

public enum BitmapStretch
{
    None,
    Fill,
    Uniform,
    UniformToFill,
    AspectFit = Uniform,
    AspectFill = UniformToFill
}

Los miembros None, Fill, Uniform y UniformToFill son los mismos que los de la enumeración Stretch de UWP. La enumeración Xamarin.FormsAspect similar define los miembros Fill, AspectFit y AspectFill.

La página Escalado uniforme que se muestra arriba centra el mapa de bits dentro del rectángulo, pero es posible que desee otras opciones, como colocar el mapa de bits en el lado izquierdo o derecho del rectángulo, o la parte superior o inferior. Ese es el propósito de la enumeración BitmapAlignment:

public enum BitmapAlignment
{
    Start,
    Center,
    End
}

La configuración de alineación no tiene ningún efecto cuando se usa con BitmapStretch.Fill.

La primera función de extensión DrawBitmap contiene un rectángulo de destino, pero ningún rectángulo de origen. Los valores predeterminados se definen para que, si desea centrar el mapa de bits, solo necesita especificar un miembro BitmapStretch:

static class BitmapExtensions
{
    ···
    public static void DrawBitmap(this SKCanvas canvas, SKBitmap bitmap, SKRect dest, 
                                  BitmapStretch stretch, 
                                  BitmapAlignment horizontal = BitmapAlignment.Center, 
                                  BitmapAlignment vertical = BitmapAlignment.Center, 
                                  SKPaint paint = null)
    {
        if (stretch == BitmapStretch.Fill)
        {
            canvas.DrawBitmap(bitmap, dest, paint);
        }
        else
        {
            float scale = 1;

            switch (stretch)
            {
                case BitmapStretch.None:
                    break;

                case BitmapStretch.Uniform:
                    scale = Math.Min(dest.Width / bitmap.Width, dest.Height / bitmap.Height);
                    break;

                case BitmapStretch.UniformToFill:
                    scale = Math.Max(dest.Width / bitmap.Width, dest.Height / bitmap.Height);
                    break;
            }

            SKRect display = CalculateDisplayRect(dest, scale * bitmap.Width, scale * bitmap.Height, 
                                                  horizontal, vertical);

            canvas.DrawBitmap(bitmap, display, paint);
        }
    }
    ···
}

El propósito principal de este método es calcular un factor de escalado denominado scale que se aplica al ancho y alto del mapa de bits al llamar al método CalculateDisplayRect. Este es el método que calcula un rectángulo para mostrar el mapa de bits en función de la alineación horizontal y vertical:

static class BitmapExtensions
{
    ···
    static SKRect CalculateDisplayRect(SKRect dest, float bmpWidth, float bmpHeight, 
                                       BitmapAlignment horizontal, BitmapAlignment vertical)
    {
        float x = 0;
        float y = 0;

        switch (horizontal)
        {
            case BitmapAlignment.Center:
                x = (dest.Width - bmpWidth) / 2;
                break;

            case BitmapAlignment.Start:
                break;

            case BitmapAlignment.End:
                x = dest.Width - bmpWidth;
                break;
        }

        switch (vertical)
        {
            case BitmapAlignment.Center:
                y = (dest.Height - bmpHeight) / 2;
                break;

            case BitmapAlignment.Start:
                break;

            case BitmapAlignment.End:
                y = dest.Height - bmpHeight;
                break;
        }

        x += dest.Left;
        y += dest.Top;

        return new SKRect(x, y, x + bmpWidth, y + bmpHeight);
    }
}

La clase BitmapExtensions contiene un método adicional DrawBitmap con un rectángulo de origen para especificar un subconjunto del mapa de bits. Este método es similar al primero, excepto que el factor de escalado se calcula en función del rectángulo source y, a continuación, se aplica al rectángulo source de la llamada a CalculateDisplayRect:

static class BitmapExtensions
{
    ···
    public static void DrawBitmap(this SKCanvas canvas, SKBitmap bitmap, SKRect source, SKRect dest,
                                  BitmapStretch stretch,
                                  BitmapAlignment horizontal = BitmapAlignment.Center,
                                  BitmapAlignment vertical = BitmapAlignment.Center,
                                  SKPaint paint = null)
    {
        if (stretch == BitmapStretch.Fill)
        {
            canvas.DrawBitmap(bitmap, source, dest, paint);
        }
        else
        {
            float scale = 1;

            switch (stretch)
            {
                case BitmapStretch.None:
                    break;

                case BitmapStretch.Uniform:
                    scale = Math.Min(dest.Width / source.Width, dest.Height / source.Height);
                    break;

                case BitmapStretch.UniformToFill:
                    scale = Math.Max(dest.Width / source.Width, dest.Height / source.Height);
                    break;
            }

            SKRect display = CalculateDisplayRect(dest, scale * source.Width, scale * source.Height, 
                                                  horizontal, vertical);

            canvas.DrawBitmap(bitmap, source, display, paint);
        }
    }
    ···
}

El primero de estos dos nuevos métodos DrawBitmap se muestra en la página Modos de escalado. El archivo XAML contiene tres elementos Picker que le permiten seleccionar miembros de las enumeraciones BitmapStretch y BitmapAlignment:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:SkiaSharpFormsDemos"
             xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             x:Class="SkiaSharpFormsDemos.Bitmaps.ScalingModesPage"
             Title="Scaling Modes">

    <Grid Padding="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <skia:SKCanvasView x:Name="canvasView" 
                           Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
                           PaintSurface="OnCanvasViewPaintSurface" />

        <Label Text="Stretch:"
               Grid.Row="1" Grid.Column="0"
               VerticalOptions="Center" />

        <Picker x:Name="stretchPicker"
                Grid.Row="1" Grid.Column="1"
                SelectedIndexChanged="OnPickerSelectedIndexChanged">
            <Picker.ItemsSource>
                <x:Array Type="{x:Type local:BitmapStretch}">
                    <x:Static Member="local:BitmapStretch.None" />
                    <x:Static Member="local:BitmapStretch.Fill" />
                    <x:Static Member="local:BitmapStretch.Uniform" />
                    <x:Static Member="local:BitmapStretch.UniformToFill" />
                </x:Array>
            </Picker.ItemsSource>

            <Picker.SelectedIndex>
                0
            </Picker.SelectedIndex>
        </Picker>

        <Label Text="Horizontal Alignment:"
               Grid.Row="2" Grid.Column="0"
               VerticalOptions="Center" />

        <Picker x:Name="horizontalPicker" 
                Grid.Row="2" Grid.Column="1"
                SelectedIndexChanged="OnPickerSelectedIndexChanged">
            <Picker.ItemsSource>
                <x:Array Type="{x:Type local:BitmapAlignment}">
                    <x:Static Member="local:BitmapAlignment.Start" />
                    <x:Static Member="local:BitmapAlignment.Center" />
                    <x:Static Member="local:BitmapAlignment.End" />
                </x:Array>
            </Picker.ItemsSource>

            <Picker.SelectedIndex>
                0
            </Picker.SelectedIndex>
        </Picker>

        <Label Text="Vertical Alignment:"
               Grid.Row="3" Grid.Column="0"
               VerticalOptions="Center" />

        <Picker x:Name="verticalPicker" 
                Grid.Row="3" Grid.Column="1"
                SelectedIndexChanged="OnPickerSelectedIndexChanged">
            <Picker.ItemsSource>
                <x:Array Type="{x:Type local:BitmapAlignment}">
                    <x:Static Member="local:BitmapAlignment.Start" />
                    <x:Static Member="local:BitmapAlignment.Center" />
                    <x:Static Member="local:BitmapAlignment.End" />
                </x:Array>
            </Picker.ItemsSource>

            <Picker.SelectedIndex>
                0
            </Picker.SelectedIndex>
        </Picker>
    </Grid>
</ContentPage>

El archivo de código subyacente simplemente invalida el CanvasView cuando cualquier elemento Picker ha cambiado. El controlador PaintSurface accede a las tres vistas Picker para llamar al método de extensión DrawBitmap:

public partial class ScalingModesPage : ContentPage
{
    SKBitmap bitmap =
        BitmapExtensions.LoadBitmapResource(typeof(ScalingModesPage),
                                            "SkiaSharpFormsDemos.Media.Banana.jpg");
    public ScalingModesPage()
    {
        InitializeComponent();
    }

    private void OnPickerSelectedIndexChanged(object sender, EventArgs args)
    {
        canvasView.InvalidateSurface();
    }

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

        canvas.Clear();

        SKRect dest = new SKRect(0, 0, info.Width, info.Height);

        BitmapStretch stretch = (BitmapStretch)stretchPicker.SelectedItem;
        BitmapAlignment horizontal = (BitmapAlignment)horizontalPicker.SelectedItem;
        BitmapAlignment vertical = (BitmapAlignment)verticalPicker.SelectedItem;

        canvas.DrawBitmap(bitmap, dest, stretch, horizontal, vertical);
    }
}

Estas son algunas combinaciones de opciones:

Scaling Modes

La página Subconjunto de rectángulo tiene prácticamente el mismo archivo XAML que los modos de escalado, pero el archivo de código subyacente define un subconjunto rectangular del mapa de bits proporcionado por el campo SOURCE:

public partial class ScalingModesPage : ContentPage
{
    SKBitmap bitmap =
        BitmapExtensions.LoadBitmapResource(typeof(ScalingModesPage),
                                            "SkiaSharpFormsDemos.Media.Banana.jpg");

    static readonly SKRect SOURCE = new SKRect(94, 12, 212, 118);

    public RectangleSubsetPage()
    {
        InitializeComponent();
    }

    private void OnPickerSelectedIndexChanged(object sender, EventArgs args)
    {
        canvasView.InvalidateSurface();
    }

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

        canvas.Clear();

        SKRect dest = new SKRect(0, 0, info.Width, info.Height);

        BitmapStretch stretch = (BitmapStretch)stretchPicker.SelectedItem;
        BitmapAlignment horizontal = (BitmapAlignment)horizontalPicker.SelectedItem;
        BitmapAlignment vertical = (BitmapAlignment)verticalPicker.SelectedItem;

        canvas.DrawBitmap(bitmap, SOURCE, dest, stretch, horizontal, vertical);
    }
}

Este origen de rectángulo aísla la cabeza del mono, como se muestra en estas capturas de pantalla:

Rectangle Subset