SkiaSharp-Bitmap-Tiling
Wie Sie in den beiden vorherigen Artikeln gesehen haben, kann die SKShader
Klasse lineare oder kreisförmige Farbverläufe erstellen. Dieser Artikel konzentriert sich auf das SKShader
Objekt, das eine Bitmap zum Kacheln eines Bereichs verwendet. Die Bitmap kann horizontal und vertikal wiederholt werden, entweder in der ursprünglichen Ausrichtung oder alternativ horizontal und vertikal gekippt. Durch das Kippen werden Unterbrechungen zwischen den Kacheln vermieden:
Die statische SKShader.CreateBitmap
Methode, die diesen Shader erstellt, weist einen SKBitmap
Parameter und zwei Member der SKShaderTileMode
Enumeration auf:
public static SKShader CreateBitmap (SKBitmap src, SKShaderTileMode tmx, SKShaderTileMode tmy)
Die beiden Parameter geben die Modi an, die für horizontale Kacheln und vertikale Kacheln verwendet werden. Dies ist dieselbe SKShaderTileMode
Aufzählung, die auch mit den Farbverlaufsmethoden verwendet wird.
Eine CreateBitmap
Überladung enthält ein SKMatrix
Argument, um eine Transformation für die nebeneinander angeordneten Bitmaps auszuführen:
public static SKShader CreateBitmap (SKBitmap src, SKShaderTileMode tmx, SKShaderTileMode tmy, SKMatrix localMatrix)
Dieser Artikel enthält mehrere Beispiele für die Verwendung dieser Matrixtransformation mit nebeneinander angeordneten Bitmaps.
Erkunden der Kachelmodi
Das erste Programm im Abschnitt "Bitmap-Tiling " der Shader und anderer Effekte-Seite des Beispiels veranschaulicht die Effekte der beiden SKShaderTileMode
Argumente. Die XAML-Datei "Bitmapkachel flip modi " instanziiert eine SKCanvasView
und zwei Picker
Ansichten, mit denen Sie einen SKShaderTilerMode
Wert für horizontale und vertikale Kacheln auswählen können. Beachten Sie, dass ein Array der SKShaderTileMode
Member im Resources
Abschnitt definiert ist:
<?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>
Der Konstruktor der CodeBehind-Datei wird in der Bitmapressource geladen, die einen Affen anzeigt, der sitzt. Zunächst wird das Bild mit der ExtractSubset
Methode SKBitmap
abgerissen, sodass der Kopf und die Füße die Ränder der Bitmap berühren. Der Konstruktor verwendet dann die Resize
Methode, um eine weitere Bitmap der Hälfte der Größe zu erstellen. Durch diese Änderungen wird die Bitmap etwas besser für die Kachelung geeignet:
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);
}
}
}
Der PaintSurface
Handler ruft die SKShaderTileMode
Einstellungen aus den beiden Picker
Ansichten ab und erstellt ein SKShader
Objekt basierend auf der Bitmap und diesen beiden Werten. Dieser Shader wird verwendet, um den Zeichenbereich auszufüllen:
Der iOS-Bildschirm links zeigt den Effekt der Standardwerte von SKShaderTileMode.Clamp
. Die Bitmap befindet sich in der oberen linken Ecke. Unterhalb der Bitmap wird die untere Zeile der Pixel ganz nach unten wiederholt. Rechts neben der Bitmap wird die äußerst rechte Spalte der Pixel ganz nach oben wiederholt. Der Re Standard der des Zeichenbereichs wird durch das dunkelbraune Pixel in der unteren rechten Ecke der Bitmap gefärbt. Es sollte offensichtlich sein, dass die Clamp
Option fast nie mit Bitmap-Tiling verwendet wird!
Der Android-Bildschirm in der Mitte zeigt das Ergebnis für SKShaderTileMode.Repeat
beide Argumente an. Die Kachel wird horizontal und vertikal wiederholt. Der Universelle Windows-Plattform Bildschirm wird angezeigtSKShaderTileMode.Mirror
. Die Kacheln werden wiederholt, aber abwechselnd horizontal und vertikal gekippt. Der Vorteil dieser Option besteht darin, dass zwischen den Kacheln keine Diskontitäten vorhanden sind.
Denken Sie daran, dass Sie verschiedene Optionen für die horizontale und vertikale Wiederholung verwenden können. Sie können als zweites Argument angeben SKShaderTileMode.Mirror
, CreateBitmap
aber SKShaderTileMode.Repeat
als drittes Argument. In jeder Zeile wechseln die Affen immer noch zwischen dem normalen Bild und dem Spiegel Bild, aber keiner der Affen ist auf dem Kopf.
Gemusterte Hintergründe
Bitmap-Kacheln werden häufig verwendet, um einen gemusterten Hintergrund aus einer relativ kleinen Bitmap zu erstellen. Das klassische Beispiel ist eine Ziegelwand.
Das Algorithmic Brick Wall-Zeichenblatt erstellt eine kleine Bitmap, die einem ganzen Ziegel und zwei Hälften eines durch Mörtel getrennten Ziegels ähnelt. Da dieser Ziegel auch im nächsten Beispiel verwendet wird, wird er von einem statischen Konstruktor erstellt und mit einer statischen Eigenschaft öffentlich gemacht:
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; }
···
}
Die resultierende Bitmap ist 70 Pixel breit und 60 Pixel hoch:
Der Rest der Algorithmic Brick Wall-Seite erstellt ein SKShader
Objekt, das dieses Bild horizontal und vertikal wiederholt:
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);
}
}
}
Das Ergebnis lautet wie folgt:
Vielleicht bevorzugen Sie etwas realistischer. In diesem Fall können Sie ein Foto einer tatsächlichen Ziegelwand aufnehmen und dann zuschneiden. Diese Bitmap ist 300 Pixel breit und 150 Pixel hoch:
Diese Bitmap wird auf der Seite "Photographic Brick Wall " verwendet:
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);
}
}
}
Beachten Sie, dass die SKShaderTileMode
argumente CreateBitmap
beide Mirror
sind. Diese Option ist in der Regel erforderlich, wenn Sie Kacheln verwenden, die aus realen Bildern erstellt wurden. Durch das Spiegeln der Kacheln werden Nichtigkeiten vermieden:
Einige Arbeiten sind erforderlich, um eine geeignete Bitmap für die Kachel zu erhalten. Das funktioniert nicht sehr gut, weil der dunkleere Ziegel zu viel hervorsticht. Es erscheint regelmäßig in den wiederholten Bildern und zeigt die Tatsache, dass diese Ziegelwand aus einer kleineren Bitmap konstruiert wurde.
Der Medienordner des Beispiels enthält auch dieses Bild einer Steinwand:
Die ursprüngliche Bitmap ist jedoch etwas zu groß für eine Kachel. Die Größe könnte geändert werden, aber die SKShader.CreateBitmap
Methode kann auch die Größe der Kachel ändern, indem sie eine Transformation darauf anwendet. Diese Option wird auf der Seite Steinwand gezeigt:
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);
}
}
}
Ein SKMatrix
Wert wird erstellt, um das Bild auf die Hälfte seiner Originalgröße zu skalieren:
Funktioniert die Transformation mit der ursprünglichen Bitmap, die in der CreateBitmap
Methode verwendet wird? Oder transformiert es das resultierende Array von Kacheln?
Eine einfache Möglichkeit, diese Frage zu beantworten, besteht darin, eine Drehung als Teil der Transformation einzuschließen:
SKMatrix matrix = SKMatrix.MakeScale(0.5f, 0.5f);
SKMatrix.PostConcat(ref matrix, SKMatrix.MakeRotationDegrees(15));
Wenn die Transformation auf die einzelne Kachel angewendet wird, sollte jedes wiederholte Bild der Kachel gedreht werden, und das Ergebnis würde viele Unterbrechungen enthalten. Aus diesem Screenshot ist jedoch offensichtlich, dass das zusammengesetzte Array von Kacheln transformiert wird:
Im Abschnitt "Kachelausrichtung" sehen Sie ein Beispiel für eine Übersetzungstransformation, die auf den Shader angewendet wird.
Das Beispiel simuliert einen Holzkornhintergrund mit Bitmap-Tiling basierend auf dieser 240-Pixel-quadratischen Bitmap:
Das ist ein Foto eines Holzbodens. Die SKShaderTileMode.Mirror
Option ermöglicht es, als viel größere Fläche von Holz zu erscheinen:
Kachelausrichtung
Alle bisher gezeigten Beispiele haben den shader verwendet, der erstellt SKShader.CreateBitmap
wurde, um den gesamten Zeichenbereich abzudecken. In den meisten Fällen verwenden Sie Bitmap-Tiling für die Ablage kleinerer Bereiche oder (seltener), um die Innenbereiche von dicken Linien zu füllen. Hier ist die fotografische Ziegelwandkachel, die für ein kleineres Rechteck verwendet wird:
Dies sieht für Sie möglicherweise in Ordnung aus oder nicht. Vielleicht sind Sie gestört, dass das Kachelmuster nicht mit einem vollständigen Ziegel in der oberen linken Ecke des Rechtecks beginnt. Das liegt daran, dass Shader an der Canvas und nicht an dem grafischen Objekt ausgerichtet sind, das sie schmücken.
Der Fix ist einfach. Erstellen Sie einen SKMatrix
Wert basierend auf einer Übersetzungstransformation. Die Transformation verschiebt das nebeneinander angeordnete Muster effektiv an den Punkt, an dem die obere linke Ecke der Kachel ausgerichtet werden soll. Dieser Ansatz wird auf der Seite "Kachelausrichtung " veranschaulicht, auf der das Bild der oben gezeigten nicht ausgerichteten Kacheln erstellt wurde:
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);
}
}
}
Die Seite "Kachelausrichtung " enthält eine TapGestureRecognizer
. Tippen oder klicken Sie auf den Bildschirm, und das Programm wechselt mit einem SKMatrix
Argument zur SKShader.CreateBitmap
Methode. Diese Transformation verschiebt das Muster so, dass die obere linke Ecke einen vollständigen Ziegel enthält:
Sie können diese Technik auch verwenden, um sicherzustellen, dass das nebeneinander angeordnete Bitmapmuster innerhalb des Bereichs zentriert ist, in dem es zeichnet. Auf der Seite "Zentrierte Kacheln " berechnet der PaintSurface
Handler zunächst Koordinaten, als ob die einzelne Bitmap in der Mitte des Zeichenbereichs angezeigt wird. Anschließend werden diese Koordinaten verwendet, um eine Übersetzungstransformation für SKShader.CreateBitmap
. Diese Transformation verschiebt das gesamte Muster so, dass eine Kachel zentriert ist:
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);
}
}
}
Der PaintSurface
Handler endet mit dem Zeichnen eines Kreises in der Mitte des Zeichenbereichs. Sicher genug, eine der Kacheln befindet sich genau in der Mitte des Kreises, und die anderen werden in einem symmetrischen Muster angeordnet:
Ein weiterer Zentrierungsansatz ist eigentlich etwas einfacher. Anstatt eine Übersetzungstransformation zu erstellen, die eine Kachel in der Mitte platziert, können Sie eine Ecke des nebeneinander angeordneten Musters zentrieren. Verwenden Sie im SKMatrix.MakeTranslation
Aufruf Argumente für die Mitte des Zeichenbereichs:
SKMatrix matrix = SKMatrix.MakeTranslation(info.Rect.MidX, info.Rect.MidY);
Das Muster ist immer noch zentriert und symmetrisch, aber keine Kachel befindet sich in der Mitte:
Vereinfachung durch Drehung
Manchmal kann die Verwendung einer Drehtransformation in der SKShader.CreateBitmap
Methode die Bitmapkachel vereinfachen. Dies wird deutlich, wenn versucht wird, eine Kachel für einen Kettenverknüpfungszaun zu definieren. Die ChainLinkTile.cs Datei erstellt die hier gezeigte Kachel (mit einem rosa Hintergrund für Klarheit):
Die Kachel muss zwei Verknüpfungen enthalten, sodass der Code die Kachel in vier Quadranten aufteilt. Die Quadranten oben links und unten rechts sind identisch, aber sie sind nicht vollständig. Die Kabel haben kleine Noten, die mit einer zusätzlichen Zeichnung in den oberen rechten und unteren linken Quadranten behandelt werden müssen. Die Datei, die all diese Arbeit ausführt, ist 174 Zeilen lang.
Es stellt sich heraus, dass es viel einfacher ist, diese Kachel zu erstellen:
Wenn der Bitmapkachel-Shader um 90 Grad gedreht wird, sind die visuellen Elemente fast gleich.
Der Code zum Erstellen der einfacheren Kettenverknüpfungskachel ist Teil der Chain-Link-Kachelseite . Der Konstruktor bestimmt eine Kachelgröße basierend auf dem Gerätetyp, auf dem das Programm ausgeführt wird, und ruft dann auf CreateChainLinkTile
, was auf der Bitmap mit Linien, Pfaden und Farbverlaufs-Shadern zeichnet:
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;
}
}
···
}
Mit Ausnahme der Kabel ist die Kachel transparent, was bedeutet, dass Sie sie über etwas anderes anzeigen können. Das Programm wird in einer der Bitmapressourcen geladen, zeigt sie an, um den Zeichenbereich auszufüllen, und zeichnet dann den Shader oben:
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);
}
}
}
Beachten Sie, dass der Shader um 45 Grad gedreht wird, sodass er wie ein echter Kettenverknüpfungszaun ausgerichtet ist:
Animieren von Bitmapkacheln
Sie können ein gesamtes Bitmap-Kachelmuster animieren, indem Sie die Matrixtransformation animieren. Vielleicht möchten Sie, dass das Muster horizontal oder vertikal oder beides verschoben werden soll. Dazu können Sie eine Übersetzungstransformation basierend auf den Verschiebungskoordinaten erstellen.
Es ist auch möglich, auf eine kleine Bitmap zu zeichnen oder die Pixelbits der Bitmap mit der Geschwindigkeit von 60 Mal pro Sekunde zu bearbeiten. Diese Bitmap kann dann für die Tilung verwendet werden, und das gesamte nebeneinander angeordnete Muster kann animiert werden.
Auf der Seite "Animierte Bitmapkachel " wird dieser Ansatz veranschaulicht. Eine Bitmap wird als Feld instanziiert, das 64 Pixel groß ist. Der Konstruktor ruft DrawBitmap
auf, um ihm eine anfängliche Darstellung zu verleihen. Wenn das angle
Feld null ist (wie beim ersten Aufruf der Methode), enthält die Bitmap zwei Linien, die als X gekreuzt sind. Die Linien sind lang genug, um unabhängig vom angle
Wert immer zum Rand der Bitmap zu gelangen:
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);
}
}
···
}
Der Animationsaufwand tritt in den OnAppearing
Und OnDisappearing
Außerkraftsetzungen auf. Die OnTimerTick
Methode animiert den angle
Wert von 0 Grad bis 360 Grad alle 10 Sekunden, um die X-Abbildung innerhalb der Bitmap zu drehen:
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;
}
···
}
Aufgrund der Symmetrie der X-Abbildung entspricht dies dem Drehen des angle
Werts von 0 Grad auf 90 Grad alle 2,5 Sekunden.
Der PaintSurface
Handler erstellt einen Shader aus der Bitmap und verwendet das Paint-Objekt, um den gesamten Zeichenbereich zu färben:
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);
}
}
}
Die SKShaderTileMode.Mirror
Optionen stellen sicher, dass die Arme des X in jeder Bitmap-Verknüpfung mit dem X in den angrenzenden Bitmaps ein allgemeines animiertes Muster erstellen, das viel komplexer erscheint, als die einfache Animation vorschlagen würde: