Affiancamento bitmap skiaSharp
Come si è visto nei due articoli precedenti, la SKShader
classe può creare sfumature lineari o circolari. Questo articolo è incentrato sull'oggetto SKShader
che usa una bitmap per affiancare un'area. La bitmap può essere ripetuta orizzontalmente e verticalmente, nell'orientamento originale o in alternativa capovolta orizzontalmente e verticalmente. Il capovolgimento evita interruzioni tra i riquadri:
Il metodo statico SKShader.CreateBitmap
che crea questo shader ha un SKBitmap
parametro e due membri dell'enumerazione SKShaderTileMode
:
public static SKShader CreateBitmap (SKBitmap src, SKShaderTileMode tmx, SKShaderTileMode tmy)
I due parametri indicano le modalità usate per la tillatura orizzontale e la tillatura verticale. Si tratta della stessa SKShaderTileMode
enumerazione usata anche con i metodi sfumatura.
Un CreateBitmap
overload include un SKMatrix
argomento per eseguire una trasformazione nelle bitmap affiancate:
public static SKShader CreateBitmap (SKBitmap src, SKShaderTileMode tmx, SKShaderTileMode tmy, SKMatrix localMatrix)
Questo articolo contiene diversi esempi di utilizzo di questa trasformazione matrice con bitmap affiancate.
Esplorazione delle modalità riquadro
Il primo programma nella sezione Associazione bitmap degli shader e di altri effetti dell'esempio illustra gli effetti dei due SKShaderTileMode
argomenti. Il file XAML Bitmap Tile Flip Mode crea un'istanza di e SKCanvasView
due Picker
visualizzazioni che consentono di selezionare un SKShaderTilerMode
valore per la tillatura orizzontale e verticale. Si noti che nella sezione è definita Resources
una matrice dei SKShaderTileMode
membri:
<?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>
Il costruttore del file code-behind viene caricato nella risorsa bitmap che mostra una scimmia seduta. Prima di tutto ritaglia l'immagine usando il ExtractSubset
metodo di SKBitmap
in modo che la testa e i piedi tocchino i bordi della bitmap. Il costruttore usa quindi il Resize
metodo per creare un'altra bitmap di metà delle dimensioni. Queste modifiche rendono la bitmap un po'più adatta per la tiling:
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);
}
}
}
Il PaintSurface
gestore ottiene le SKShaderTileMode
impostazioni dalle due Picker
visualizzazioni e crea un SKShader
oggetto in base alla bitmap e a questi due valori. Questo shader viene usato per riempire l'area di disegno:
La schermata iOS a sinistra mostra l'effetto dei valori predefiniti di SKShaderTileMode.Clamp
. La bitmap si trova nell'angolo superiore sinistro. Sotto la bitmap, la riga inferiore di pixel viene ripetuta fino in basso. A destra della bitmap, la colonna più a destra di pixel viene ripetuta in tutto il percorso. Il resto dell'area di disegno è colorato dal pixel marrone scuro nell'angolo inferiore destro della bitmap. Dovrebbe essere ovvio che l'opzione Clamp
è quasi mai usata con la legatura bitmap!
La schermata Android al centro mostra il risultato di SKShaderTileMode.Repeat
per entrambi gli argomenti. Il riquadro viene ripetuto orizzontalmente e verticalmente. La schermata piattaforma UWP (Universal Windows Platform) mostra SKShaderTileMode.Mirror
. I riquadri vengono ripetuti ma in alternativa capovolti orizzontalmente e verticalmente. Il vantaggio di questa opzione è che non vi sono interruzioni tra i riquadri.
Tenere presente che è possibile usare diverse opzioni per la ripetizione orizzontale e verticale. È possibile specificare SKShaderTileMode.Mirror
come secondo argomento, CreateBitmap
ma SKShaderTileMode.Repeat
come terzo argomento. Su ogni riga, le scimmie si alternano ancora tra l'immagine normale e l'immagine speculare, ma nessuno delle scimmie è capovolto.
Sfondi con pattern
L'associazione bitmap viene comunemente usata per creare uno sfondo basato su criteri da una bitmap relativamente piccola. L'esempio classico è un muro di mattoni.
La pagina Algorithmic Brick Wall crea una piccola bitmap che assomiglia a un intero mattoni e due metà di un mattone separato da malta. Poiché questo mattoni viene usato anche nell'esempio successivo, viene creato da un costruttore statico e reso pubblico con una proprietà statica:
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; }
···
}
La bitmap risultante ha una larghezza di 70 pixel e un'altezza di 60 pixel:
Il resto della pagina Algorithmic Brick Wall crea un SKShader
oggetto che ripete l'immagine orizzontalmente e 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);
}
}
}
Il risultato è il seguente:
Potresti preferire qualcosa di più realistico. In tal caso, è possibile scattare una fotografia di un muro di mattoni effettivo e quindi ritagliarlo. Questa bitmap ha una larghezza di 300 pixel e un'altezza di 150 pixel:
Questa bitmap viene usata nella pagina Muro di mattoni fotografici:
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);
}
}
}
Si noti che gli SKShaderTileMode
argomenti di CreateBitmap
sono entrambi Mirror
. Questa opzione è in genere necessaria quando si usano riquadri creati da immagini reali. Il mirroring dei riquadri evita interruzioni:
Alcune operazioni sono necessarie per ottenere una bitmap adatta per il riquadro. Questo non funziona molto bene perché il mattoni più scuro si distingue troppo. Appare regolarmente all'interno delle immagini ripetute, rivelando il fatto che questo muro di mattoni è stato costruito da una bitmap più piccola.
La cartella Media dell'esempio include anche questa immagine di una parete in pietra:
Tuttavia, la bitmap originale è un po ' troppo grande per un riquadro. Potrebbe essere ridimensionato, ma il SKShader.CreateBitmap
metodo può anche ridimensionare il riquadro applicando una trasformazione. Questa opzione è illustrata nella pagina Stone Wall :
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);
}
}
}
Viene creato un SKMatrix
valore per ridimensionare l'immagine a metà delle dimensioni originali:
La trasformazione opera sulla bitmap originale usata nel CreateBitmap
metodo ? Oppure trasforma la matrice risultante di riquadri?
Un modo semplice per rispondere a questa domanda consiste nell'includere una rotazione come parte della trasformazione:
SKMatrix matrix = SKMatrix.MakeScale(0.5f, 0.5f);
SKMatrix.PostConcat(ref matrix, SKMatrix.MakeRotationDegrees(15));
Se la trasformazione viene applicata al singolo riquadro, ogni immagine ripetuta del riquadro deve essere ruotata e il risultato conterrà molte discontinuità. Tuttavia, è ovvio da questo screenshot che la matrice composita di riquadri viene trasformata:
Nella sezione Allineamento riquadro verrà visualizzato un esempio di trasformazione traduci applicata allo shader.
L'esempio simula uno sfondo con granularità di legno usando la tiling bitmap basata su questa bitmap quadrata a 240 pixel:
Questa è una fotografia di un pavimento in legno. L'opzione SKShaderTileMode.Mirror
consente di apparire come un'area di legno molto più grande:
Allineamento dei riquadri
Tutti gli esempi mostrati finora hanno usato lo shader creato da SKShader.CreateBitmap
per coprire l'intera area di disegno. Nella maggior parte dei casi, si userà la legatura bitmap per l'archiviazione di aree più piccole o (più raramente) per riempire gli interni di linee spesse. Ecco il riquadro a parete fotografica usato per un rettangolo più piccolo:
Questo potrebbe sembrare bene per te, o forse no. Forse sei disturbato che il motivo di tillatura non inizia con un mattoni pieno nell'angolo superiore sinistro del rettangolo. Questo perché gli shader sono allineati all'area di disegno e non all'oggetto grafico che adornano.
La correzione è semplice. Creare un SKMatrix
valore basato su una trasformazione di traduzione. La trasformazione sposta in modo efficace il modello affiancato nel punto in cui si desidera che l'angolo superiore sinistro del riquadro venga allineato. Questo approccio è illustrato nella pagina Allineamento riquadri, che ha creato l'immagine dei riquadri non allineati illustrati sopra:
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 pagina Allineamento riquadri include un oggetto TapGestureRecognizer
. Toccare o fare clic sulla schermata e il programma passa al SKShader.CreateBitmap
metodo con un SKMatrix
argomento. Questa trasformazione sposta il motivo in modo che l'angolo superiore sinistro contenga un mattone pieno:
È anche possibile usare questa tecnica per assicurarsi che il modello bitmap affiancato sia centrato all'interno dell'area che disegna. Nella pagina Riquadri centrati il PaintSurface
gestore calcola innanzitutto le coordinate come se visualizzerà la singola bitmap al centro dell'area di disegno. Usa quindi queste coordinate per creare una trasformazione traduci per SKShader.CreateBitmap
. Questa trasformazione sposta l'intero modello in modo che un riquadro sia centrato:
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);
}
}
}
Il PaintSurface
gestore si conclude disegnando un cerchio al centro dell'area di disegno. Abbastanza sicuro, uno dei riquadri è esattamente al centro del cerchio, e gli altri sono disposti in un modello simmetrico:
Un altro approccio centrato è in realtà un po'più semplice. Invece di costruire una trasformazione di traslazione che inserisce un riquadro al centro, è possibile centrare un angolo del modello affiancato. SKMatrix.MakeTranslation
Nella chiamata usare gli argomenti per il centro dell'area di disegno:
SKMatrix matrix = SKMatrix.MakeTranslation(info.Rect.MidX, info.Rect.MidY);
Il modello è ancora centrato e simmetrico, ma nessun riquadro si trova al centro:
Semplificazione tramite rotazione
A volte l'uso di una trasformazione di rotazione nel SKShader.CreateBitmap
metodo può semplificare il riquadro bitmap. Questo diventa evidente quando si tenta di definire un riquadro per un recinto di collegamento a catena. Il file ChainLinkTile.cs crea il riquadro mostrato qui (con uno sfondo rosa per motivi di chiarezza):
Il riquadro deve includere due collegamenti, in modo che il codice divide il riquadro in quattro quadranti. I quadranti in alto a sinistra e in basso a destra sono gli stessi, ma non sono completi. I fili hanno piccole tacche che devono essere gestite con un disegno aggiuntivo nei quadranti in alto a destra e in basso a sinistra. Il file che esegue tutto questo lavoro è lungo 174 righe.
Risulta molto più semplice creare questo riquadro:
Se lo shader bitmap-tile è ruotato di 90 gradi, gli oggetti visivi sono quasi uguali.
Il codice per creare il riquadro concatenamento più semplice fa parte della pagina Chain-Link Tile . Il costruttore determina una dimensione del riquadro in base al tipo di dispositivo su cui è in esecuzione il programma e quindi chiama CreateChainLinkTile
, che disegna sulla bitmap usando linee, percorsi e shader sfumature:
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;
}
}
···
}
Ad eccezione dei fili, il riquadro è trasparente, il che significa che è possibile visualizzarlo sopra qualcos'altro. Il programma carica in una delle risorse bitmap, lo visualizza per riempire l'area di disegno e quindi disegna lo shader in alto:
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);
}
}
}
Si noti che lo shader viene ruotato di 45 gradi in modo che sia orientato come un vero recinto a catena:
Animazione dei riquadri bitmap
È possibile animare un intero modello bitmap-tile animando la trasformazione matrice. È possibile che il modello si sposti orizzontalmente o verticalmente o entrambi. A tale scopo, è possibile creare una trasformazione di conversione in base alle coordinate di spostamento.
È anche possibile disegnare su una piccola bitmap o di modificare i bit pixel della bitmap alla velocità di 60 volte al secondo. Tale bitmap può quindi essere usata per l'associazione e l'intero modello affiancato può sembrare animato.
La pagina Riquadro bitmap animata illustra questo approccio. Viene creata un'istanza di una bitmap come campo con un quadrato di 64 pixel. Il costruttore chiama DrawBitmap
per dare un aspetto iniziale. Se il angle
campo è zero (così com'è quando il metodo viene chiamato per la prima volta), la bitmap contiene due linee incrociate come X. Le righe sono rese sufficientemente lunghe da raggiungere sempre il bordo della bitmap indipendentemente dal angle
valore:
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);
}
}
···
}
L'overhead dell'animazione OnAppearing
si verifica nelle sostituzioni e OnDisappearing
. Il OnTimerTick
metodo anima il angle
valore da 0 a 360 gradi ogni 10 secondi per ruotare la figura X all'interno della bitmap:
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;
}
···
}
A causa della simmetria della figura X, questo equivale a ruotare il angle
valore da 0 gradi a 90 gradi ogni 2,5 secondi.
Il PaintSurface
gestore crea uno shader dalla bitmap e usa l'oggetto paint per colorare l'intera area di disegno:
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);
}
}
}
Le SKShaderTileMode.Mirror
opzioni assicurano che le braccia della X in ogni bitmap join con la X nelle bitmap adiacenti per creare un modello animato complessivo che sembra molto più complesso rispetto all'animazione semplice suggerisce: