Mosaico de mapa de bits SkiaSharp
Como ha visto en los dos artículos anteriores, la clase SKShader
puede crear degradados lineales o circulares. Este artículo se centra en el objeto SKShader
que usa un mapa de bits para representar un área en mosaico. El mapa de bits puede repetirse horizontal y verticalmente, en su orientación original o volteado alternativamente horizontal y verticalmente. El volteo evita las discontinuidades entre los mosaicos:
El método estático SKShader.CreateBitmap
que crea este sombreador tiene un parámetro SKBitmap
y dos miembros de la enumeración SKShaderTileMode
:
public static SKShader CreateBitmap (SKBitmap src, SKShaderTileMode tmx, SKShaderTileMode tmy)
Los dos parámetros indican los modos usados para el mosaico horizontal y el mosaico vertical. Se trata de la misma enumeración SKShaderTileMode
que también se utiliza con los métodos de degradado.
Una sobrecarga CreateBitmap
incluye un argumento SKMatrix
para realizar una transformación en los mapas de bits en mosaico:
public static SKShader CreateBitmap (SKBitmap src, SKShaderTileMode tmx, SKShaderTileMode tmy, SKMatrix localMatrix)
Este artículo contiene varios ejemplos de uso de esta transformación de matriz con mapas de bits en mosaico.
Exploración de los modos de mosaico
El primer programa de la sección Bitmap Tiling de la página Shaders y otros efectos de la muestra demuestra los efectos de los dos argumentos SKShaderTileMode
. El archivo XAML de Modos de volteo de mosaico de mapa de bits crea una instancia de SKCanvasView
y dos vistas Picker
que le permiten seleccionar un valor SKShaderTilerMode
para el mosaico horizontal y vertical. Observe que una matriz de los miembros SKShaderTileMode
se define en la sección Resources
:
<?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:skia="clr-namespace:SkiaSharp;assembly=SkiaSharp"
xmlns:skiaforms="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
x:Class="SkiaSharpFormsDemos.Effects.BitmapTileFlipModesPage"
Title="Bitmap Tile Flip Modes">
<ContentPage.Resources>
<x:Array x:Key="tileModes"
Type="{x:Type skia:SKShaderTileMode}">
<x:Static Member="skia:SKShaderTileMode.Clamp" />
<x:Static Member="skia:SKShaderTileMode.Repeat" />
<x:Static Member="skia:SKShaderTileMode.Mirror" />
</x:Array>
</ContentPage.Resources>
<StackLayout>
<skiaforms:SKCanvasView x:Name="canvasView"
VerticalOptions="FillAndExpand"
PaintSurface="OnCanvasViewPaintSurface" />
<Picker x:Name="xModePicker"
Title="Tile X Mode"
Margin="10, 0"
ItemsSource="{StaticResource tileModes}"
SelectedIndex="0"
SelectedIndexChanged="OnPickerSelectedIndexChanged" />
<Picker x:Name="yModePicker"
Title="Tile Y Mode"
Margin="10, 10"
ItemsSource="{StaticResource tileModes}"
SelectedIndex="0"
SelectedIndexChanged="OnPickerSelectedIndexChanged" />
</StackLayout>
</ContentPage>
El constructor del archivo de código subyacente se carga en el recurso de mapa de bits que muestra un mono sentado. Primero recorta la imagen utilizando el método ExtractSubset
de SKBitmap
para que la cabeza y los pies toquen los bordes del mapa de bits. A continuación, el constructor usa el método Resize
para crear otro mapa de bits de la mitad del tamaño. Estos cambios hacen que el mapa de bits sea un poco más adecuado para el mosaico:
public partial class BitmapTileFlipModesPage : ContentPage
{
SKBitmap bitmap;
public BitmapTileFlipModesPage ()
{
InitializeComponent ();
SKBitmap origBitmap = BitmapExtensions.LoadBitmapResource(
GetType(), "SkiaSharpFormsDemos.Media.SeatedMonkey.jpg");
// Define cropping rect
SKRectI cropRect = new SKRectI(5, 27, 296, 260);
// Get the cropped bitmap
SKBitmap croppedBitmap = new SKBitmap(cropRect.Width, cropRect.Height);
origBitmap.ExtractSubset(croppedBitmap, cropRect);
// Resize to half the width and height
SKImageInfo info = new SKImageInfo(cropRect.Width / 2, cropRect.Height / 2);
bitmap = croppedBitmap.Resize(info, SKBitmapResizeMethod.Box);
}
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();
// Get tile modes from Pickers
SKShaderTileMode xTileMode =
(SKShaderTileMode)(xModePicker.SelectedIndex == -1 ?
0 : xModePicker.SelectedItem);
SKShaderTileMode yTileMode =
(SKShaderTileMode)(yModePicker.SelectedIndex == -1 ?
0 : yModePicker.SelectedItem);
using (SKPaint paint = new SKPaint())
{
paint.Shader = SKShader.CreateBitmap(bitmap, xTileMode, yTileMode);
canvas.DrawRect(info.Rect, paint);
}
}
}
El controlador PaintSurface
obtiene la configuración SKShaderTileMode
de las dos vistas Picker
y crea un objeto SKShader
basado en el mapa de bits y esos dos valores. Este sombreador se usa para rellenar el lienzo:
La pantalla de iOS de la izquierda muestra el efecto de los valores predeterminados de SKShaderTileMode.Clamp
. El mapa de bits se encuentra en la esquina superior izquierda. Debajo del mapa de bits, la fila inferior de píxeles se repite hasta abajo. A la derecha del mapa de bits, la columna de píxeles situada más a la derecha se repite en todo el recorrido. El resto del lienzo está coloreado por el píxel marrón oscuro en la esquina inferior derecha del mapa de bits. Debe ser obvio que la opción Clamp
casi nunca se usa con la representación en mosaico del mapa de bits.
La pantalla de Android del centro muestra el resultado de SKShaderTileMode.Repeat
para ambos argumentos. El mosaico se repite horizontal y verticalmente. La pantalla de la Plataforma universal de Windows muestra SKShaderTileMode.Mirror
. Los mosaicos se repiten pero se voltean horizontal y verticalmente. La ventaja de esta opción es que no hay discontinuidades entre los mosaicos.
Tenga en cuenta que puede usar diferentes opciones para la repetición horizontal y vertical. Puede especificar SKShaderTileMode.Mirror
como segundo argumento para CreateBitmap
pero SKShaderTileMode.Repeat
como tercer argumento. En cada fila, los monos siguen alternando entre la imagen normal y la imagen reflejada, pero ninguno de los monos está al revés.
Fondos con patrones
El mosaico de mapa de bits se usa normalmente para crear un fondo con patrones a partir de un mapa de bits relativamente pequeño. El ejemplo clásico es una pared de ladrillo.
La página Muro de ladrillo algorítmico crea un pequeño mapa de bits que se asemeja a un ladrillo entero y a dos mitades de ladrillo separadas por mortero. Dado que este ladrillo también se usa en el ejemplo siguiente, se crea mediante un constructor estático y se hace público con una propiedad estática:
public class AlgorithmicBrickWallPage : ContentPage
{
static AlgorithmicBrickWallPage()
{
const int brickWidth = 64;
const int brickHeight = 24;
const int morterThickness = 6;
const int bitmapWidth = brickWidth + morterThickness;
const int bitmapHeight = 2 * (brickHeight + morterThickness);
SKBitmap bitmap = new SKBitmap(bitmapWidth, bitmapHeight);
using (SKCanvas canvas = new SKCanvas(bitmap))
using (SKPaint brickPaint = new SKPaint())
{
brickPaint.Color = new SKColor(0xB2, 0x22, 0x22);
canvas.Clear(new SKColor(0xF0, 0xEA, 0xD6));
canvas.DrawRect(new SKRect(morterThickness / 2,
morterThickness / 2,
morterThickness / 2 + brickWidth,
morterThickness / 2 + brickHeight),
brickPaint);
int ySecondBrick = 3 * morterThickness / 2 + brickHeight;
canvas.DrawRect(new SKRect(0,
ySecondBrick,
bitmapWidth / 2 - morterThickness / 2,
ySecondBrick + brickHeight),
brickPaint);
canvas.DrawRect(new SKRect(bitmapWidth / 2 + morterThickness / 2,
ySecondBrick,
bitmapWidth,
ySecondBrick + brickHeight),
brickPaint);
}
// Save as public property for other programs
BrickWallTile = bitmap;
}
public static SKBitmap BrickWallTile { private set; get; }
···
}
El mapa de bits resultante es de 70 píxeles de ancho y 60 píxeles de alto:
El resto de la página Muro de ladrillo algorítmico crea un objeto SKShader
que repite esta imagen horizontal y verticalmente:
public class AlgorithmicBrickWallPage : ContentPage
{
···
public AlgorithmicBrickWallPage ()
{
Title = "Algorithmic Brick Wall";
// Create SKCanvasView
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();
using (SKPaint paint = new SKPaint())
{
// Create bitmap tiling
paint.Shader = SKShader.CreateBitmap(BrickWallTile,
SKShaderTileMode.Repeat,
SKShaderTileMode.Repeat);
// Draw background
canvas.DrawRect(info.Rect, paint);
}
}
}
Este es el resultado:
Es posible que prefiera algo un poco más realista. En ese caso, puede tomar una fotografía de una pared de ladrillo real y luego recortarla. Este mapa de bits es de 300 píxeles de ancho y 150 píxeles de alto:
Este mapa de bits se usa en la página Muro de ladrillo fotografico.
public class PhotographicBrickWallPage : ContentPage
{
SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(
typeof(PhotographicBrickWallPage),
"SkiaSharpFormsDemos.Media.BrickWallTile.jpg");
public PhotographicBrickWallPage()
{
Title = "Photographic Brick Wall";
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();
using (SKPaint paint = new SKPaint())
{
// Create bitmap tiling
paint.Shader = SKShader.CreateBitmap(bitmap,
SKShaderTileMode.Mirror,
SKShaderTileMode.Mirror);
// Draw background
canvas.DrawRect(info.Rect, paint);
}
}
}
Observa que los argumentos SKShaderTileMode
de CreateBitmap
son ambos Mirror
. Esta opción suele ser necesaria cuando se usan mosaicos creados a partir de imágenes reales. La creación de reflejo de los mosaicos evita las discontinuidades:
Se requiere algún trabajo para obtener un mapa de bits adecuado para el mosaico. Este no funciona muy bien porque el ladrillo más oscuro destaca demasiado. Aparece regularmente en las imágenes repetidas, revelando el hecho de que esta pared de ladrillo se construyó a partir de un mapa de bits más pequeño.
La carpeta Media del ejemplo también incluye esta imagen de una pared de piedra:
Sin embargo, el mapa de bits original es demasiado grande para un mosaico. Se podría redimensionar, pero el método SKShader.CreateBitmap
también puede redimensionar el mosaico aplicándole una transformación. Esta opción se muestra en la página Muro de piedra:
public class StoneWallPage : ContentPage
{
SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(
typeof(StoneWallPage),
"SkiaSharpFormsDemos.Media.StoneWallTile.jpg");
public StoneWallPage()
{
Title = "Stone Wall";
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();
using (SKPaint paint = new SKPaint())
{
// Create scale transform
SKMatrix matrix = SKMatrix.MakeScale(0.5f, 0.5f);
// Create bitmap tiling
paint.Shader = SKShader.CreateBitmap(bitmap,
SKShaderTileMode.Mirror,
SKShaderTileMode.Mirror,
matrix);
// Draw background
canvas.DrawRect(info.Rect, paint);
}
}
}
Se crea un valor SKMatrix
para escalar la imagen a la mitad de su tamaño original:
¿Funciona la transformación en el mapa de bits original usado en el método CreateBitmap
? ¿O transforma la matriz resultante de mosaicos?
Una manera fácil de responder a esta pregunta es incluir una rotación como parte de la transformación:
SKMatrix matrix = SKMatrix.MakeScale(0.5f, 0.5f);
SKMatrix.PostConcat(ref matrix, SKMatrix.MakeRotationDegrees(15));
Si la transformación se aplica al mosaico individual, cada imagen repetida del mosaico debe girarse y el resultado contendrá muchas discontinuidades. Pero es obvio en esta captura de pantalla que se transforma la matriz compuesta de mosaicos:
En la sección Alineación de mosaicos, verá un ejemplo de una transformación de traslación aplicada al sombreador.
El ejemplo simula un fondo de grano de madera mediante mosaicos de mapa de bits basado en este mapa de bits cuadrado de 240 píxeles:
Esa es una fotografía de un suelo de madera. La opción SKShaderTileMode.Mirror
permite que aparezca como un área de madera mucho mayor:
Alineación de mosaicos
Todos los ejemplos mostrados hasta ahora han usado el sombreador creado por SKShader.CreateBitmap
para cubrir todo el lienzo. En la mayoría de los casos, usará el mosaico de mapa de bits para archivar áreas más pequeñas o (más rara vez) para rellenar los interiores de líneas gruesas. Este es el mosaico fotográfico del muro de ladrillo usado para un rectángulo más pequeño:
Esto podría parecerle bien, o quizás no. Tal vez le moleste que el patrón de mosaicos no empiece con un ladrillo completo en la esquina superior izquierda del rectángulo. Esto se debe a que los sombreadores están alineados con el lienzo y no con el objeto gráfico que adornan.
La corrección es sencilla. Cree un valor SKMatrix
basado en una transformación de traslación. La transformación desplaza eficazmente el patrón en mosaico al punto en el que desea que se alinee la esquina superior izquierda del mosaico. Este enfoque se demuestra en la página Alineación de mosaicos, que creó la imagen de los mosaicos no alineados que se muestra arriba:
public class TileAlignmentPage : ContentPage
{
bool isAligned;
public TileAlignmentPage()
{
Title = "Tile Alignment";
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
// Add tap handler
TapGestureRecognizer tap = new TapGestureRecognizer();
tap.Tapped += (sender, args) =>
{
isAligned ^= true;
canvasView.InvalidateSurface();
};
canvasView.GestureRecognizers.Add(tap);
Content = canvasView;
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPaint paint = new SKPaint())
{
SKRect rect = new SKRect(info.Width / 7,
info.Height / 7,
6 * info.Width / 7,
6 * info.Height / 7);
// Get bitmap from other program
SKBitmap bitmap = AlgorithmicBrickWallPage.BrickWallTile;
// Create bitmap tiling
if (!isAligned)
{
paint.Shader = SKShader.CreateBitmap(bitmap,
SKShaderTileMode.Repeat,
SKShaderTileMode.Repeat);
}
else
{
SKMatrix matrix = SKMatrix.MakeTranslation(rect.Left, rect.Top);
paint.Shader = SKShader.CreateBitmap(bitmap,
SKShaderTileMode.Repeat,
SKShaderTileMode.Repeat,
matrix);
}
// Draw rectangle
canvas.DrawRect(rect, paint);
}
}
}
La página Alineación de mosaicos incluye un TapGestureRecognizer
. Pulse o haga clic en la pantalla y el programa cambiará al método SKShader.CreateBitmap
con un argumento SKMatrix
. Esta transformación desplaza el patrón para que la esquina superior izquierda contenga un ladrillo completo:
También puede utilizar esta técnica para asegurarse de que el patrón de mapa de bits en mosaico está centrado dentro del área que pinta. En la página Mosaicos centrados, el controlador PaintSurface
calcula primero las coordenadas como si fuera a mostrar el mapa de bits único en el centro del lienzo. A continuación, usa esas coordenadas para crear una transformación de traslación para SKShader.CreateBitmap
. Esta transformación desplaza todo el patrón para que un mosaico se centre:
public class CenteredTilesPage : ContentPage
{
SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(
typeof(CenteredTilesPage),
"SkiaSharpFormsDemos.Media.monkey.png");
public CenteredTilesPage ()
{
Title = "Centered Tiles";
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();
// Find coordinates to center bitmap in canvas...
float x = (info.Width - bitmap.Width) / 2f;
float y = (info.Height - bitmap.Height) / 2f;
using (SKPaint paint = new SKPaint())
{
// ... but use them to create a translate transform
SKMatrix matrix = SKMatrix.MakeTranslation(x, y);
paint.Shader = SKShader.CreateBitmap(bitmap,
SKShaderTileMode.Repeat,
SKShaderTileMode.Repeat,
matrix);
// Use that tiled bitmap pattern to fill a circle
canvas.DrawCircle(info.Rect.MidX, info.Rect.MidY,
Math.Min(info.Width, info.Height) / 2,
paint);
}
}
}
El controlador PaintSurface
concluye dibujando un círculo en el centro del lienzo. Efectivamente, uno de los mosaicos está exactamente en el centro del círculo y los demás están dispuestos de forma simétrica:
Otro enfoque de centrado es en realidad un poco más fácil. En lugar de construir una transformación de traslación que coloque un mosaico en el centro, puede centrar una esquina del patrón en mosaico. En la llamada SKMatrix.MakeTranslation
, use argumentos para el centro del lienzo:
SKMatrix matrix = SKMatrix.MakeTranslation(info.Rect.MidX, info.Rect.MidY);
El patrón sigue centrado y simétrico, pero no hay ningún mosaico en el centro:
Simplificación mediante la rotación
A veces, el uso de una transformación de rotación en el método SKShader.CreateBitmap
puede simplificar el mosaico del mapa de bits. Esto resulta evidente al intentar definir un mosaico para una valla de alambre. El archivo ChainLinkTile.cs crea el mosaico que se muestra aquí (con un fondo rosa con fines de claridad):
El mosaico debe incluir dos vínculos para que el código divida el mosaico en cuatro cuadrantes. Los cuadrantes superior izquierdo e inferior derecho son iguales, pero no están completos. Los alambres tienen pequeñas muescas que deben manipularse con algún dibujo adicional en los cuadrantes superior derecho e inferior izquierdo. El archivo que hace todo este trabajo tiene 174 líneas.
Resulta mucho más fácil crear este mosaico:
Si el sombreador de mosaicos de mapa de bits gira 90 grados, los objetos visuales son casi los mismos.
El código para crear el mosaico de alambre más fácil forma parte de la página Mosaico de alambres. El constructor determina un tamaño de mosaico basado en el tipo de dispositivo en el que se está ejecutando el programa y, a continuación, llama a CreateChainLinkTile
, que dibuja en el mapa de bits utilizando líneas, trazados y sombreadores de degradado:
public class ChainLinkFencePage : ContentPage
{
···
SKBitmap tileBitmap;
public ChainLinkFencePage ()
{
Title = "Chain-Link Fence";
// Create bitmap for chain-link tiling
int tileSize = Device.Idiom == TargetIdiom.Desktop ? 64 : 128;
tileBitmap = CreateChainLinkTile(tileSize);
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
SKBitmap CreateChainLinkTile(int tileSize)
{
tileBitmap = new SKBitmap(tileSize, tileSize);
float wireThickness = tileSize / 12f;
using (SKCanvas canvas = new SKCanvas(tileBitmap))
using (SKPaint paint = new SKPaint())
{
canvas.Clear();
paint.Style = SKPaintStyle.Stroke;
paint.StrokeWidth = wireThickness;
paint.IsAntialias = true;
// Draw straight wires first
paint.Shader = SKShader.CreateLinearGradient(new SKPoint(0, 0),
new SKPoint(0, tileSize),
new SKColor[] { SKColors.Silver, SKColors.Black },
new float[] { 0.4f, 0.6f },
SKShaderTileMode.Clamp);
canvas.DrawLine(0, tileSize / 2,
tileSize / 2, tileSize / 2 - wireThickness / 2, paint);
canvas.DrawLine(tileSize, tileSize / 2,
tileSize / 2, tileSize / 2 + wireThickness / 2, paint);
// Draw curved wires
using (SKPath path = new SKPath())
{
path.MoveTo(tileSize / 2, 0);
path.LineTo(tileSize / 2 - wireThickness / 2, tileSize / 2);
path.ArcTo(wireThickness / 2, wireThickness / 2,
0,
SKPathArcSize.Small,
SKPathDirection.CounterClockwise,
tileSize / 2, tileSize / 2 + wireThickness / 2);
paint.Shader = SKShader.CreateLinearGradient(new SKPoint(0, 0),
new SKPoint(0, tileSize),
new SKColor[] { SKColors.Silver, SKColors.Black },
null,
SKShaderTileMode.Clamp);
canvas.DrawPath(path, paint);
path.Reset();
path.MoveTo(tileSize / 2, tileSize);
path.LineTo(tileSize / 2 + wireThickness / 2, tileSize / 2);
path.ArcTo(wireThickness / 2, wireThickness / 2,
0,
SKPathArcSize.Small,
SKPathDirection.CounterClockwise,
tileSize / 2, tileSize / 2 - wireThickness / 2);
paint.Shader = SKShader.CreateLinearGradient(new SKPoint(0, 0),
new SKPoint(0, tileSize),
new SKColor[] { SKColors.White, SKColors.Silver },
null,
SKShaderTileMode.Clamp);
canvas.DrawPath(path, paint);
}
return tileBitmap;
}
}
···
}
Excepto por los alambres, el mosaico es transparente, lo que significa que puede mostrarlo encima de otra cosa. El programa se carga en uno de los recursos de mapa de bits, lo muestra para rellenar el lienzo y, a continuación, dibuja el sombreador en la parte superior:
public class ChainLinkFencePage : ContentPage
{
SKBitmap monkeyBitmap = BitmapExtensions.LoadBitmapResource(
typeof(ChainLinkFencePage), "SkiaSharpFormsDemos.Media.SeatedMonkey.jpg");
···
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
canvas.DrawBitmap(monkeyBitmap, info.Rect, BitmapStretch.UniformToFill,
BitmapAlignment.Center, BitmapAlignment.Start);
using (SKPaint paint = new SKPaint())
{
paint.Shader = SKShader.CreateBitmap(tileBitmap,
SKShaderTileMode.Repeat,
SKShaderTileMode.Repeat,
SKMatrix.MakeRotationDegrees(45));
canvas.DrawRect(info.Rect, paint);
}
}
}
Observe que el sombreador gira 45 grados, de modo que está orientado como una valla de alambres real:
Animación de mosaicos de mapas de bits
Puede animar un patrón de mosaico de mapa de bits completo animando la transformación de matriz. Tal vez desee que el patrón se mueva horizontal o verticalmente, o ambas cosas. Puede hacerlo mediante la creación de una transformación de traslación basada en las coordenadas de desplazamiento.
También es posible dibujar en un mapa de bits pequeño, o manipular los bits de píxel del mapa de bits a una velocidad de 60 veces por segundo. Ese mapa de bits se puede usar para la representación en mosaico y todo el patrón en mosaico puede parecer animado.
En la página Mosaico de mapa de bits animado se muestra este enfoque. Se crea una instancia de un mapa de bits como un campo cuadrado de 64 píxeles. El constructor llama a DrawBitmap
para darle una apariencia inicial. Si el campo angle
es cero (como cuando se llama al método por primera vez), el mapa de bits contiene dos líneas cruzadas como una X. Las líneas se hacen lo suficientemente largas como para llegar siempre al borde del mapa de bits, independientemente del valor angle
:
public class AnimatedBitmapTilePage : ContentPage
{
const int SIZE = 64;
SKCanvasView canvasView;
SKBitmap bitmap = new SKBitmap(SIZE, SIZE);
float angle;
···
public AnimatedBitmapTilePage ()
{
Title = "Animated Bitmap Tile";
// Initialize bitmap prior to animation
DrawBitmap();
// Create SKCanvasView
canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
···
void DrawBitmap()
{
using (SKCanvas canvas = new SKCanvas(bitmap))
using (SKPaint paint = new SKPaint())
{
paint.Style = SKPaintStyle.Stroke;
paint.Color = SKColors.Blue;
paint.StrokeWidth = SIZE / 8;
canvas.Clear();
canvas.Translate(SIZE / 2, SIZE / 2);
canvas.RotateDegrees(angle);
canvas.DrawLine(-SIZE, -SIZE, SIZE, SIZE, paint);
canvas.DrawLine(-SIZE, SIZE, SIZE, -SIZE, paint);
}
}
···
}
La sobrecarga de animación se produce en las invalidaciones OnAppearing
y OnDisappearing
. El método OnTimerTick
anima el valor angle
de 0 grados a 360 grados cada 10 segundos para girar la figura X en el mapa de bits:
public class AnimatedBitmapTilePage : ContentPage
{
···
// For animation
bool isAnimating;
Stopwatch stopwatch = new Stopwatch();
···
protected override void OnAppearing()
{
base.OnAppearing();
isAnimating = true;
stopwatch.Start();
Device.StartTimer(TimeSpan.FromMilliseconds(16), OnTimerTick);
}
protected override void OnDisappearing()
{
base.OnDisappearing();
stopwatch.Stop();
isAnimating = false;
}
bool OnTimerTick()
{
const int duration = 10; // seconds
angle = (float)(360f * (stopwatch.Elapsed.TotalSeconds % duration) / duration);
DrawBitmap();
canvasView.InvalidateSurface();
return isAnimating;
}
···
}
Debido a la simetría de la figura X, esto es lo mismo que girar el valor angle
de 0 grados a 90 grados cada 2,5 segundos.
El controlador PaintSurface
crea un sombreador a partir del mapa de bits y usa el objeto paint para colorear todo el lienzo:
public class AnimatedBitmapTilePage : ContentPage
{
···
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPaint paint = new SKPaint())
{
paint.Shader = SKShader.CreateBitmap(bitmap,
SKShaderTileMode.Mirror,
SKShaderTileMode.Mirror);
canvas.DrawRect(info.Rect, paint);
}
}
}
Las opciones SKShaderTileMode.Mirror
garantizan que los brazos de la X en cada mapa de bits se unan a la X en los mapas de bits adyacentes para crear un patrón animado general que parece mucho más complejo de lo que sugeriría la simple animación: