Trame bitmap SkiaSharp
Comme vous l’avez vu dans les deux articles précédents, la SKShader
classe peut créer des dégradés linéaires ou circulaires. Cet article se concentre sur l’objet SKShader
qui utilise une bitmap pour vignetter une zone. La bitmap peut être répétée horizontalement et verticalement, soit dans son orientation d’origine, soit retournée horizontalement et verticalement. Le découpage évite les discontinuités entre les vignettes :
La méthode statique SKShader.CreateBitmap
qui crée ce nuanceur a un SKBitmap
paramètre et deux membres de l’énumération SKShaderTileMode
:
public static SKShader CreateBitmap (SKBitmap src, SKShaderTileMode tmx, SKShaderTileMode tmy)
Les deux paramètres indiquent les modes utilisés pour le mosaïque horizontal et le mosaïque vertical. Il s’agit de la même SKShaderTileMode
énumération que celle utilisée avec les méthodes de dégradé.
Une CreateBitmap
surcharge inclut un argument permettant d’effectuer SKMatrix
une transformation sur les bitmaps en mosaïques :
public static SKShader CreateBitmap (SKBitmap src, SKShaderTileMode tmx, SKShaderTileMode tmy, SKMatrix localMatrix)
Cet article contient plusieurs exemples d’utilisation de cette transformation de matrice avec des bitmaps en mosaïques.
Exploration des modes de vignette
Le premier programme de la section Mosaïques des nuanceurs et d’autres pages Effets de l’exemple illustre les effets des deux SKShaderTileMode
arguments. Le fichier XAML Mode De vignette Bitmap instancie un SKCanvasView
et deux Picker
affichages qui vous permettent de sélectionner une SKShaderTilerMode
valeur pour la mosaïque horizontale et verticale. Notez qu’un tableau des SKShaderTileMode
membres est défini dans la Resources
section :
<?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>
Le constructeur du fichier code-behind se charge dans la ressource bitmap qui affiche un singe assis. Il rogne d’abord l’image à l’aide de la ExtractSubset
méthode de SKBitmap
façon à ce que la tête et les pieds touchent les bords de la bitmap. Le constructeur utilise ensuite la Resize
méthode pour créer une autre bitmap de moitié de la taille. Ces modifications rendent l’image bitmap un peu plus adaptée au 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);
}
}
}
Le PaintSurface
gestionnaire obtient les SKShaderTileMode
paramètres des deux Picker
vues et crée un SKShader
objet basé sur la bitmap et ces deux valeurs. Ce nuanceur est utilisé pour remplir le canevas :
L’écran iOS à gauche affiche l’effet des valeurs par défaut de SKShaderTileMode.Clamp
. La bitmap se trouve dans le coin supérieur gauche. En dessous de la bitmap, la ligne inférieure des pixels est répétée jusqu’à la fin. À droite de la bitmap, la colonne la plus à droite des pixels est répétée tout au long de l’ensemble. Le reste du canevas est coloré par le pixel brun foncé dans le coin inférieur droit de l’image bitmap. Il doit être évident que l’option Clamp
n’est presque jamais utilisée avec le mosaïsme bitmap !
L’écran Android dans le centre affiche le résultat des SKShaderTileMode.Repeat
deux arguments. La vignette est répétée horizontalement et verticalement. L’écran plateforme Windows universelle affiche SKShaderTileMode.Mirror
. Les vignettes sont répétées, mais retournées horizontalement et verticalement. L’avantage de cette option est qu’il n’existe aucune discontinuité entre les vignettes.
N’oubliez pas que vous pouvez utiliser différentes options pour la répétition horizontale et verticale. Vous pouvez spécifier SKShaderTileMode.Mirror
comme deuxième argument, CreateBitmap
mais SKShaderTileMode.Repeat
comme troisième argument. Sur chaque ligne, les singes alternent toujours entre l’image normale et l’image miroir, mais aucun des singes n’est à l’envers.
Arrière-plans motifs
Le mosaïcage bitmap est couramment utilisé pour créer un arrière-plan modéré à partir d’une image bitmap relativement petite. L’exemple classique est un mur en briques.
La page Mur de brique algorithmique crée une petite bitmap qui ressemble à une brique entière et deux moitiés d’une brique séparée par le mortier. Étant donné que cette brique est également utilisée dans l’exemple suivant, elle est créée par un constructeur statique et rendue publique avec une propriété statique :
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 résultante est de 70 pixels de large et de 60 pixels de haut :
Le reste de la page Mur de brique algorithmique crée un SKShader
objet qui répète cette image horizontalement et verticalement :
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);
}
}
}
Voici le résultat :
Vous préférerez peut-être quelque chose d’un peu plus réaliste. Dans ce cas, vous pouvez prendre une photo d’un mur de briques réel, puis le rogner. Cette bitmap est de 300 pixels larges et de 150 pixels de haut :
Cette bitmap est utilisée dans la page Mur de briques photographiques :
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);
}
}
}
Notez que les SKShaderTileMode
arguments à prendre CreateBitmap
sont tous les deux Mirror
. Cette option est généralement nécessaire lorsque vous utilisez des vignettes créées à partir d’images réelles. La mise en miroir des vignettes évite les discontinuités :
Certains travaux sont nécessaires pour obtenir une bitmap appropriée pour la vignette. Celui-ci ne fonctionne pas très bien parce que la brique plus sombre se distingue trop. Il apparaît régulièrement dans les images répétées, révélant le fait que ce mur de briques a été construit à partir d’une image bitmap plus petite.
Le dossier Media de l’exemple inclut également cette image d’un mur en pierre :
Toutefois, la bitmap d’origine est un peu trop grande pour une vignette. Elle peut être redimensionnée, mais la SKShader.CreateBitmap
méthode peut également redimensionner la vignette en lui appliquant une transformation. Cette option est illustrée dans la page Mur de pierre :
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);
}
}
}
Une SKMatrix
valeur est créée pour mettre à l’échelle l’image à moitié sa taille d’origine :
La transformation fonctionne-t-elle sur la bitmap d’origine utilisée dans la CreateBitmap
méthode ? Ou est-ce qu’il transforme le tableau résultant de vignettes ?
Un moyen simple de répondre à cette question consiste à inclure une rotation dans le cadre de la transformation :
SKMatrix matrix = SKMatrix.MakeScale(0.5f, 0.5f);
SKMatrix.PostConcat(ref matrix, SKMatrix.MakeRotationDegrees(15));
Si la transformation est appliquée à la vignette individuelle, chaque image répétée de la vignette doit être pivotée et le résultat contient de nombreuses discontinuités. Mais il est évident de cette capture d’écran que le tableau composite de vignettes est transformé :
Dans l’alignement des vignettes de section, vous verrez un exemple de transformation de traduction appliquée au nuanceur.
L’exemple simule un arrière-plan en bois à l’aide d’un mosaïques bitmap basé sur cette bitmap carrée de 240 pixels :
C’est une photo d’un plancher en bois. L’option SKShaderTileMode.Mirror
lui permet d’apparaître comme une zone de bois beaucoup plus grande :
Alignement des vignettes
Tous les exemples présentés jusqu’à présent ont utilisé le nuanceur créé pour SKShader.CreateBitmap
couvrir l’intégralité du canevas. Dans la plupart des cas, vous utiliserez le mosaïque bitmap pour déposer des zones plus petites ou (plus rarement) pour remplir les intérieurs de lignes épaisses. Voici la mosaïque en briques photographiques utilisée pour un rectangle plus petit :
Cela peut vous sembler correct, ou peut-être pas. Peut-être que vous êtes perturbé que le motif de mosaïne ne commence pas par une brique complète dans le coin supérieur gauche du rectangle. C’est parce que les nuanceurs sont alignés avec le canevas et non l’objet graphique qu’ils ornent.
Le correctif est simple. Créez une SKMatrix
valeur basée sur une transformation de traduction. La transformation déplace efficacement le modèle en mosaïque jusqu’au point où vous souhaitez que le coin supérieur gauche de la vignette soit aligné. Cette approche est illustrée dans la page Alignement des vignettes, qui a créé l’image des vignettes non alignées ci-dessus :
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 page Alignement des vignettes inclut un TapGestureRecognizer
. Appuyez ou cliquez sur l’écran, puis le programme bascule vers la SKShader.CreateBitmap
méthode avec un SKMatrix
argument. Cette transformation déplace le modèle afin que l’angle supérieur gauche contienne une brique complète :
Vous pouvez également utiliser cette technique pour vous assurer que le modèle bitmap en mosaïque est centré dans la zone qu’il peint. Dans la page Vignettes centrées, le PaintSurface
gestionnaire calcule d’abord les coordonnées comme s’il s’agit d’afficher la seule bitmap au centre du canevas. Il utilise ensuite ces coordonnées pour créer une transformation de traduction pour SKShader.CreateBitmap
. Cette transformation déplace l’ensemble du modèle afin qu’une vignette soit centrée :
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);
}
}
}
Le PaintSurface
gestionnaire conclut en dessinant un cercle au centre du canevas. Bien sûr, l’une des vignettes est exactement au centre du cercle, et les autres sont disposées dans un modèle symétrique :
Une autre approche centrée est en fait un peu plus facile. Au lieu de construire une transformation de traduction qui place une vignette dans le centre, vous pouvez centrer un coin du modèle mosaïque. Dans l’appel SKMatrix.MakeTranslation
, utilisez des arguments pour le centre du canevas :
SKMatrix matrix = SKMatrix.MakeTranslation(info.Rect.MidX, info.Rect.MidY);
Le modèle est toujours centré et symétrique, mais aucune vignette n’est au centre :
Simplification par rotation
Parfois, l’utilisation d’une transformation de rotation dans la SKShader.CreateBitmap
méthode peut simplifier la vignette bitmap. Cela devient évident lors de la tentative de définition d’une vignette pour une clôture de chaînage. Le fichier ChainLinkTile.cs crée la vignette présentée ici (avec un arrière-plan rose à des fins de clarté) :
La vignette doit inclure deux liens, afin que le code divise la vignette en quatre quadrants. Les quadrants supérieur gauche et inférieur droit sont identiques, mais ils ne sont pas complets. Les fils ont peu de pouces qui doivent être gérés avec un dessin supplémentaire dans les quadrants supérieur droit et inférieur gauche. Le fichier qui effectue tout ce travail est long de 174 lignes.
Il s’avère beaucoup plus facile de créer cette vignette :
Si le nuanceur de mosaïque bitmap est pivoté de 90 degrés, les visuels sont presque identiques.
Le code permettant de créer la vignette de liaison de chaîne plus facile fait partie de la page Mosaïque chaîné . Le constructeur détermine une taille de vignette en fonction du type d’appareil sur lequel le programme s’exécute, puis appelle CreateChainLinkTile
, qui s’appuie sur la bitmap à l’aide de lignes, de chemins et de nuanceurs de dégradé :
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;
}
}
···
}
À l’exception des fils, la vignette est transparente, ce qui signifie que vous pouvez l’afficher au-dessus de quelque chose d’autre. Le programme se charge dans l’une des ressources bitmap, l’affiche pour remplir le canevas, puis dessine le nuanceur en haut :
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);
}
}
}
Notez que le nuanceur est pivoté de 45 degrés afin qu’il soit orienté comme une clôture de liaison de chaîne réelle :
Animation des vignettes bitmap
Vous pouvez animer un modèle de mosaïque bitmap entier en animant la transformation de matrice. Peut-être souhaitez-vous que le modèle se déplace horizontalement ou verticalement ou les deux. Pour ce faire, vous pouvez créer une transformation de traduction basée sur les coordonnées de déplacement.
Il est également possible de dessiner sur une petite bitmap ou de manipuler les bits de pixels de la bitmap à la vitesse de 60 fois par seconde. Cette bitmap peut ensuite être utilisée pour le mosaïque, et l’ensemble du modèle en mosaïque peut sembler animé.
La page Vignette Bitmap animée illustre cette approche. Une bitmap est instanciée sous la forme d’un champ de 64 pixels carrés. Le constructeur appelle DrawBitmap
pour lui donner une apparence initiale. Si le angle
champ est égal à zéro (comme c’est le cas lorsque la méthode est appelée pour la première fois), l’image bitmap contient deux lignes croisées sous la forme d’un X. Les lignes sont suffisamment longues pour atteindre toujours le bord de la bitmap, quelle que soit la angle
valeur :
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 surcharge d’animation se produit dans les OnAppearing
remplacements et OnDisappearing
les remplacements. La OnTimerTick
méthode anime la angle
valeur de 0 degrés à 360 degrés toutes les 10 secondes pour faire pivoter la figure X dans la 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;
}
···
}
En raison de la symétrie de la figure X, il s’agit de la même chose que la rotation de la angle
valeur de 0 degrés à 90 degrés toutes les 2,5 secondes.
Le PaintSurface
gestionnaire crée un nuanceur à partir de l’image bitmap et utilise l’objet paint pour colorer l’intégralité du canevas :
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);
}
}
}
Les SKShaderTileMode.Mirror
options garantissent que les bras du X dans chaque jointure bitmap avec le X dans les bitmaps adjacentes pour créer un modèle animé global qui semble beaucoup plus complexe que l’animation simple suggère :