Erstellen und Zeichnen auf SkiaSharp-Bitmaps

Sie haben gesehen, wie eine Anwendung Bitmaps aus dem Web, aus Anwendungsressourcen und aus der Fotobibliothek des Benutzers laden kann. Es ist auch möglich, neue Bitmaps in Ihrer Anwendung zu erstellen. Der einfachste Ansatz umfasst einen der Konstruktoren von SKBitmap:

SKBitmap bitmap = new SKBitmap(width, height);

Die width Parameter sind height ganze Zahlen und geben die Pixelabmessungen der Bitmap an. Dieser Konstruktor erstellt eine vollfarbige Bitmap mit vier Bytes pro Pixel: jeweils ein Byte für die Komponenten Rot, Grün, Blau und Alpha (Deckkraft).

Nachdem Sie eine neue Bitmap erstellt haben, müssen Sie etwas auf der Oberfläche der Bitmap abrufen. Dies erfolgt in der Regel auf eine von zwei Arten:

  • Zeichnen Sie auf der Bitmap mithilfe standardmäßiger Canvas Zeichenmethoden.
  • Greifen Sie direkt auf die Pixelbits zu.

In diesem Artikel wird der erste Ansatz veranschaulicht:

Zeichnungsbeispiel

Der zweite Ansatz wird im Artikel Accessing SkiaSharp Bitmap Pixels erläutert.

Zeichnen auf der Bitmap

Das Zeichnen auf der Oberfläche einer Bitmap entspricht dem Zeichnen auf einer Videoanzeige. Zum Zeichnen auf einer Videoanzeige erhalten Sie ein SKCanvas Objekt aus den PaintSurface Ereignisargumenten. Zum Zeichnen auf einer Bitmap erstellen Sie ein SKCanvas Objekt mit dem SKCanvas Konstruktor:

SKCanvas canvas = new SKCanvas(bitmap);

Wenn Sie mit der Zeichnung auf der Bitmap fertig sind, können Sie das SKCanvas Objekt löschen. Aus diesem Grund wird der SKCanvas Konstruktor in der Regel in einer using Anweisung aufgerufen:

using (SKCanvas canvas = new SKCanvas(bitmap))
{
    ··· // call drawing function
}

Die Bitmap kann dann angezeigt werden. Zu einem späteren Zeitpunkt kann das Programm ein neues SKCanvas Objekt erstellen, das auf derselben Bitmap basiert, und es noch etwas mehr zeichnen.

Die Hello Bitmap-Seite in der Beispielanwendung schreibt den Text "Hello, Bitmap!" in eine Bitmap und zeigt diese Bitmap dann mehrmals an.

Der Konstruktor des Objekts HelloBitmapPage beginnt mit dem Erstellen eines SKPaint Objekts zum Anzeigen von Text. Sie bestimmt die Abmessungen einer Textzeichenfolge und erstellt eine Bitmap mit diesen Dimensionen. Anschließend wird ein SKCanvas Objekt erstellt, das auf dieser Bitmap basiert, aufruft Clearund dann aufruft DrawText. Es ist immer ratsam, eine neue Bitmap aufzurufen Clear , da eine neu erstellte Bitmap zufällige Daten enthalten kann.

Der Konstruktor schließt ab, indem ein SKCanvasView Objekt zum Anzeigen der Bitmap erstellt wird:

public partial class HelloBitmapPage : ContentPage
{
    const string TEXT = "Hello, Bitmap!";
    SKBitmap helloBitmap;

    public HelloBitmapPage()
    {
        Title = TEXT;

        // Create bitmap and draw on it
        using (SKPaint textPaint = new SKPaint { TextSize = 48 })
        {
            SKRect bounds = new SKRect();
            textPaint.MeasureText(TEXT, ref bounds);

            helloBitmap = new SKBitmap((int)bounds.Right,
                                       (int)bounds.Height);

            using (SKCanvas bitmapCanvas = new SKCanvas(helloBitmap))
            {
                bitmapCanvas.Clear();
                bitmapCanvas.DrawText(TEXT, 0, -bounds.Top, textPaint);
            }
        }

        // Create SKCanvasView to view result
        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(SKColors.Aqua);

        for (float y = 0; y < info.Height; y += helloBitmap.Height)
            for (float x = 0; x < info.Width; x += helloBitmap.Width)
            {
                canvas.DrawBitmap(helloBitmap, x, y);
            }
    }
}

Der PaintSurface Handler rendert die Bitmap mehrmals in Zeilen und Spalten der Anzeige. Beachten Sie, dass die Clear Methode im PaintSurface Handler über ein Argument verfügt SKColors.Aqua, von dem der Hintergrund der Anzeigeoberfläche farben wird:

Hallo, Bitmap!

Die Darstellung des Aquahintergrunds zeigt an, dass die Bitmap mit Ausnahme des Texts transparent ist.

Clearing und Transparenz

Die Anzeige der Hello Bitmap-Seite zeigt, dass die vom Programm erstellte Bitmap mit Ausnahme des schwarzen Texts transparent ist. Aus diesem Grund zeigt die Aquafarbe der Anzeigeoberfläche durch.

Die Dokumentation der Clear Methoden beschreibt SKCanvas sie mit der Anweisung: "Ersetzt alle Pixel im aktuellen Clip des Zeichenbereichs". Die Verwendung des Worts "replaces" zeigt ein wichtiges Merkmal dieser Methoden: Alle Zeichenmethoden, um SKCanvas der vorhandenen Anzeigeoberfläche etwas hinzuzufügen. Die Clear Methoden ersetzen , was bereits vorhanden ist.

Clear ist in zwei verschiedenen Versionen vorhanden:

  • Die Clear Methode mit einem SKColor Parameter ersetzt die Pixel der Anzeigeoberfläche durch Pixel dieser Farbe.

  • Die Clear Methode ohne Parameter ersetzt die Pixel durch die SKColors.Empty Farbe, bei der alle Komponenten (Rot, Grün, Blau und Alpha) auf Null festgelegt sind. Diese Farbe wird manchmal als "transparent schwarz" bezeichnet.

Das Aufrufen Clear ohne Argumente für eine neue Bitmap initialisiert die gesamte Bitmap vollständig transparent. Im Anschluss an die Bitmap gezeichnete Elemente sind in der Regel undurchsichtig oder teilweise undurchsichtig.

Hier ist etwas zu versuchen: Ersetzen Sie auf der Seite "Hello Bitmap " die Clear methode, die auf die bitmapCanvas folgende angewendet wurde:

bitmapCanvas.Clear(new SKColor(255, 0, 0, 128));

Die Reihenfolge der SKColor Konstruktorparameter ist rot, grün, blau und alpha, wobei jeder Wert zwischen 0 und 255 liegen kann. Beachten Sie, dass ein Alphawert von 0 transparent ist, während ein Alphawert von 255 undurchsichtig ist.

Der Wert (255, 0, 0, 128) löscht die Bitmappixel auf rote Pixel mit einer Deckkraft von 50 %. Dies bedeutet, dass der Bitmaphintergrund halbtransparent ist. Der halbtransparente rote Hintergrund der Bitmap kombiniert mit dem Aquahintergrund der Anzeigeoberfläche, um einen grauen Hintergrund zu erstellen.

Versuchen Sie, die Farbe des Texts auf transparent schwarz festzulegen, indem Sie die folgende Zuordnung in den SKPaint Initialisierer setzen:

Color = new SKColor(0, 0, 0, 0)

Möglicherweise denken Sie, dass dieser transparente Text vollständig transparente Bereiche der Bitmap erstellt, durch die der Aquahintergrund der Anzeigeoberfläche angezeigt wird. Aber das ist nicht so. Der Text wird über dem, was sich bereits auf der Bitmap befindet, gezeichnet. Der transparente Text ist überhaupt nicht sichtbar.

Keine Draw Methode macht jemals eine Bitmap transparenter. Dies kann nur Clear möglich sein.

Bitmapfarbtypen

Mit dem einfachsten SKBitmap Konstruktor können Sie eine ganzzahlige Pixelbreite und -höhe für die Bitmap angeben. Andere SKBitmap Konstruktoren sind komplexer. Für diese Konstruktoren sind Argumente von zwei Enumerationstypen erforderlich: SKColorType und SKAlphaType. Andere Konstruktoren verwenden die SKImageInfo Struktur, die diese Informationen konsolidiert.

Die SKColorType Aufzählung hat 9 Member. Jeder dieser Member beschreibt eine bestimmte Möglichkeit zum Speichern der Bitmappixel:

  • Unknown
  • Alpha8 — jedes Pixel ist 8 Bit, die einen Alphawert von vollständig transparent bis vollständig undurchsichtig darstellen
  • Rgb565 — jedes Pixel beträgt 16 Bit, 5 Bit für Rot und Blau und 6 für Grün
  • Argb4444 — jedes Pixel beträgt 16 Bit, 4 für Alpha, Rot, Grün und Blau
  • Rgba8888 — jedes Pixel beträgt 32 Bit, 8 für Rot, Grün, Blau und Alpha.
  • Bgra8888 — jedes Pixel beträgt 32 Bit, 8 für Blau, Grün, Rot und Alpha.
  • Index8 — jedes Pixel ist 8 Bit und stellt einen Index in einem SKColorTable
  • Gray8 — Jedes Pixel ist 8 Bit, die einen grauen Schatten von Schwarz bis Weiß darstellen
  • RgbaF16 — jedes Pixel ist 64 Bit, mit rot, grün, blau und alpha in einem 16-Bit-Gleitkommaformat

Die beiden Formate, bei denen jedes Pixel 32 Pixel (4 Byte) beträgt, werden häufig als Vollfarbformate bezeichnet. Viele der anderen Formate sind ab einer Zeit, zu der Videoanzeigen selbst nicht in der Lage waren, vollfarbig zu sein. Bitmaps mit eingeschränkter Farbe waren für diese Displays ausreichend und erlaubten Bitmaps, weniger Speicherplatz im Arbeitsspeicher zu belegen.

Heutzutage verwenden Programmierer fast immer vollfarbige Bitmaps und stören sich nicht mit anderen Formaten. Die Ausnahme ist das RgbaF16 Format, das eine größere Farbauflösung als auch die Vollfarbformate zulässt. Dieses Format wird jedoch für spezielle Zwecke verwendet, z. B. medizinische Bildgebung, und macht nicht viel Sinn, wenn es mit Standard-Vollfarbanzeigen verwendet wird.

Diese Artikelreihe schränkt sich auf die SKBitmap Standardmäßig verwendeten Farbformate ein, wenn kein SKColorType Element angegeben wird. Dieses Standardformat basiert auf der zugrunde liegenden Plattform. Für die plattformen, die von Xamarin.Formsunterstützt werden, lautet der Standardfarbtyp:

  • Rgba8888 für iOS und Android
  • Bgra8888 für die UWP

Der einzige Unterschied ist die Reihenfolge der 4 Bytes im Arbeitsspeicher, und dies wird nur dann zu einem Problem, wenn Sie direkt auf die Pixelbits zugreifen. Dies wird erst dann wichtig, wenn Sie zum Artikel "Zugreifen auf SkiaSharp Bitmap Pixels" gelangen.

Die Enumeration SKAlphaType hat vier Mitglieder:

  • Unknown
  • Opaque — Die Bitmap hat keine Transparenz.
  • Premul — Farbkomponenten werden durch die Alphakomponente vorab multipliziert
  • Unpremul — Farbkomponenten werden von der Alphakomponente nicht vorvervielfältigt.

Hier ist ein 4-Byte-rote Bitmappixel mit 50 % Transparenz, wobei die Bytes in der Reihenfolge Rot, Grün, Blau, Alpha angezeigt werden:

0xFF 0x00 0x00 0x80

Wenn eine Bitmap mit semitransparenten Pixeln auf einer Anzeigeoberfläche gerendert wird, müssen die Farbkomponenten jedes Bitmappixels mit dem Alphawert dieses Pixels multipliziert werden, und die Farbkomponenten des entsprechenden Pixels der Anzeigeoberfläche müssen mit 255 minus dem Alphawert multipliziert werden. Die beiden Pixel können dann kombiniert werden. Die Bitmap kann schneller gerendert werden, wenn die Farbkomponenten in den Bitmappixeln bereits durch den Alphawert vorab multipliziert wurden. Das gleiche rote Pixel würde wie folgt in einem vorab multiplizierten Format gespeichert werden:

0x80 0x00 0x00 0x80

Diese Leistungsverbesserung ist der Grund, warum SkiaSharp Bitmaps standardmäßig mit einem Premul Format erstellt werden. Aber auch hier ist es notwendig, dies nur zu wissen, wenn Sie auf Pixelbits zugreifen und diese bearbeiten.

Zeichnen auf vorhandenen Bitmaps

Es ist nicht erforderlich, eine neue Bitmap zu erstellen, um darauf zu zeichnen. Sie können auch auf eine vorhandene Bitmap zeichnen.

Die Monkey Moustache-Seite verwendet den Konstruktor, um das MonkeyFace.png Bild zu laden. Anschließend wird ein SKCanvas Objekt erstellt, das auf dieser Bitmap basiert, und verwendet SKPaint und SKPath Objekte, um einen Schnurrlaken darauf zu zeichnen:

public partial class MonkeyMoustachePage : ContentPage
{
    SKBitmap monkeyBitmap;

    public MonkeyMoustachePage()
    {
        Title = "Monkey Moustache";

        monkeyBitmap = BitmapExtensions.LoadBitmapResource(GetType(),
            "SkiaSharpFormsDemos.Media.MonkeyFace.png");

        // Create canvas based on bitmap
        using (SKCanvas canvas = new SKCanvas(monkeyBitmap))
        {
            using (SKPaint paint = new SKPaint())
            {
                paint.Style = SKPaintStyle.Stroke;
                paint.Color = SKColors.Black;
                paint.StrokeWidth = 24;
                paint.StrokeCap = SKStrokeCap.Round;

                using (SKPath path = new SKPath())
                {
                    path.MoveTo(380, 390);
                    path.CubicTo(560, 390, 560, 280, 500, 280);

                    path.MoveTo(320, 390);
                    path.CubicTo(140, 390, 140, 280, 200, 280);

                    canvas.DrawPath(path, paint);
                }
            }
        }

        // Create SKCanvasView to view result
        SKCanvasView canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;
    }

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

        canvas.Clear();
        canvas.DrawBitmap(monkeyBitmap, info.Rect, BitmapStretch.Uniform);
    }
}

Der Konstruktor schließt durch Erstellen eines SKCanvasView Handlers, dessen PaintSurface Handler einfach das Ergebnis anzeigt:

Monkey Moustache

Kopieren und Ändern von Bitmaps

Zu den Methoden SKCanvas , die Sie zum Zeichnen auf einer Bitmap verwenden können, gehören DrawBitmap. Dies bedeutet, dass Sie eine Bitmap auf eine andere zeichnen können, in der Regel in irgendeiner Weise ändern.

Die vielseitigste Möglichkeit zum Ändern einer Bitmap besteht darin, auf die tatsächlichen Pixelbits zuzugreifen, ein Thema, das im Artikel "Zugreifen auf SkiaSharp-Bitmappixel" behandelt wird. Es gibt jedoch viele andere Techniken zum Ändern von Bitmaps, die keinen Zugriff auf die Pixelbits erfordern.

Die folgende Bitmap, die in der Beispielanwendung enthalten ist, beträgt 360 Pixel breit und 480 Pixel in der Höhe:

Bergsteiger

Angenommen, Sie haben die Erlaubnis des Affen auf der linken Seite nicht erhalten, um dieses Foto zu veröffentlichen. Eine Lösung besteht darin, das Gesicht des Affen mit einer Technik zu verdecken, die als Pixelisierung bezeichnet wird. Die Pixel der Oberfläche werden durch Farbblöcke ersetzt, sodass Sie die Features nicht herausstellen können. Die Farbblöcke werden in der Regel vom Originalbild abgeleitet, indem die Farben der Pixel, die diesen Blöcken entsprechen, durchschnittlich ergibt. Aber Sie müssen diese Durchschnittlichung nicht selbst durchführen. Dies geschieht automatisch, wenn Sie eine Bitmap in eine kleinere Pixeldimension kopieren.

Das Gesicht des linken Affen belegt ungefähr einen Quadratbereich von 72 Pixeln mit einer oberen linken Ecke am Punkt (112, 238). Ersetzen wir diesen Quadratbereich mit 72 Pixeln durch ein 9:9-Array farbiger Blöcke, von denen jede 8 x 8 Pixel quadratisch ist.

Die Pixelgröße-Bildseite wird in dieser Bitmap geladen und erstellt zuerst eine winzige 9-Pixel-Quadrat-Bitmap namens faceBitmap. Dies ist ein Ziel für das Kopieren nur des Affengesichts. Das Zielrechteck ist nur 9 Pixel quadratisch, das Quellrechteck ist jedoch 72 Pixel quadratisch. Jeder 8:8-Block von Quellpixeln wird durch Durchschnittlichkeit der Farben auf nur ein Pixel konsolidiert.

Der nächste Schritt besteht darin, die ursprüngliche Bitmap in eine neue Bitmap mit der gleichen Größe zu kopieren, die aufgerufen wird pixelizedBitmap. Der Winzige faceBitmap wird dann mit einem quadratischen Zielrechteck von 72 Pixeln darüber kopiert, sodass jedes Pixel faceBitmap um 8 Mal seine Größe erweitert wird:

public class PixelizedImagePage : ContentPage
{
    SKBitmap pixelizedBitmap;

    public PixelizedImagePage ()
    {
        Title = "Pixelize Image";

        SKBitmap originalBitmap = BitmapExtensions.LoadBitmapResource(GetType(),
            "SkiaSharpFormsDemos.Media.MountainClimbers.jpg");

        // Create tiny bitmap for pixelized face
        SKBitmap faceBitmap = new SKBitmap(9, 9);

        // Copy subset of original bitmap to that
        using (SKCanvas canvas = new SKCanvas(faceBitmap))
        {
            canvas.Clear();
            canvas.DrawBitmap(originalBitmap,
                              new SKRect(112, 238, 184, 310),   // source
                              new SKRect(0, 0, 9, 9));          // destination

        }

        // Create full-sized bitmap for copy
        pixelizedBitmap = new SKBitmap(originalBitmap.Width, originalBitmap.Height);

        using (SKCanvas canvas = new SKCanvas(pixelizedBitmap))
        {
            canvas.Clear();

            // Draw original in full size
            canvas.DrawBitmap(originalBitmap, new SKPoint());

            // Draw tiny bitmap to cover face
            canvas.DrawBitmap(faceBitmap,
                              new SKRect(112, 238, 184, 310));  // destination
        }

        // Create SKCanvasView to view result
        SKCanvasView canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;
    }

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

        canvas.Clear();
        canvas.DrawBitmap(pixelizedBitmap, info.Rect, BitmapStretch.Uniform);
    }
}

Der Konstruktor schließt durch Erstellen eines SKCanvasView Zum Anzeigens des Ergebnisses:

Pixelgröße (Bild)

Drehen von Bitmaps

Eine weitere gängige Aufgabe ist das Drehen von Bitmaps. Dies ist besonders hilfreich beim Abrufen von Bitmaps aus einer i Telefon- oder iPad-Fotobibliothek. Sofern das Gerät nicht in einer bestimmten Ausrichtung gehalten wurde, wenn das Foto aufgenommen wurde, ist das Bild wahrscheinlich auf dem Kopf oder seitwärts.

Wenn Sie eine Bitmap auf den Kopf stellen, müssen Sie eine andere Bitmap mit der gleichen Größe wie die erste Bitmap erstellen und dann eine Transformation so festlegen, dass sie um 180 Grad gedreht wird, während Sie die erste in die Zweite kopieren. In allen Beispielen in diesem Abschnitt ist das SKBitmap Objekt, bitmap das Sie drehen müssen:

SKBitmap rotatedBitmap = new SKBitmap(bitmap.Width, bitmap.Height);

using (SKCanvas canvas = new SKCanvas(rotatedBitmap))
{
    canvas.Clear();
    canvas.RotateDegrees(180, bitmap.Width / 2, bitmap.Height / 2);
    canvas.DrawBitmap(bitmap, new SKPoint());
}

Beim Drehen um 90 Grad müssen Sie eine Bitmap erstellen, die eine andere Größe als das Original aufweist, indem Sie die Höhe und Breite austauschen. Wenn die ursprüngliche Bitmap beispielsweise 1200 Pixel breit und 800 Pixel hoch ist, beträgt die gedrehte Bitmap 800 Pixel breit und 1200 Pixel breit. Legen Sie Die Übersetzung und Drehung so fest, dass die Bitmap um die obere linke Ecke gedreht und dann in die Ansicht verschoben wird. (Denken Sie daran, dass die Translate Methoden RotateDegrees in der entgegengesetzten Reihenfolge der Anwendung aufgerufen werden.) Hier ist der Code zum Drehen von 90 Grad im Uhrzeigersinn:

SKBitmap rotatedBitmap = new SKBitmap(bitmap.Height, bitmap.Width);

using (SKCanvas canvas = new SKCanvas(rotatedBitmap))
{
    canvas.Clear();
    canvas.Translate(bitmap.Height, 0);
    canvas.RotateDegrees(90);
    canvas.DrawBitmap(bitmap, new SKPoint());
}

Und hier ist eine ähnliche Funktion zum Drehen von 90 Grad gegen den Uhrzeigersinn:

SKBitmap rotatedBitmap = new SKBitmap(bitmap.Height, bitmap.Width);

using (SKCanvas canvas = new SKCanvas(rotatedBitmap))
{
    canvas.Clear();
    canvas.Translate(0, bitmap.Width);
    canvas.RotateDegrees(-90);
    canvas.DrawBitmap(bitmap, new SKPoint());
}

Diese beiden Methoden werden auf den im Artikel Zuschneiden von SkiaSharp Bitmaps beschriebenen Foto-Puzzle-Seiten verwendet.

Ein Programm, mit dem der Benutzer eine Bitmap in 90-Grad-Schritten drehen kann, muss nur eine Funktion zum Drehen um 90 Grad implementiert werden. Der Benutzer kann dann in jedem Schritt um 90 Grad drehen, indem er die Ausführung dieser funktion wiederholt durchführt.

Ein Programm kann auch eine Bitmap um eine beliebige Menge drehen. Ein einfacher Ansatz besteht darin, die Funktion zu ändern, die um 180 Grad gedreht wird, indem 180 durch eine generalisierte angle Variable ersetzt wird:

SKBitmap rotatedBitmap = new SKBitmap(bitmap.Width, bitmap.Height);

using (SKCanvas canvas = new SKCanvas(rotatedBitmap))
{
    canvas.Clear();
    canvas.RotateDegrees(angle, bitmap.Width / 2, bitmap.Height / 2);
    canvas.DrawBitmap(bitmap, new SKPoint());
}

Im Allgemeinen wird diese Logik jedoch von den Ecken der gedrehten Bitmap abgeschnitten. Ein besserer Ansatz besteht darin, die Größe der gedrehten Bitmap mithilfe der Trigonometrie zu berechnen, um diese Ecken einzuschließen.

Diese Trigonometrie wird auf der Bitmap-Rotatorseite angezeigt. Die XAML-Datei instanziiert eine SKCanvasView und eine Slider , die zwischen 0 und 360 Grad liegen kann, wobei der Label aktuelle Wert angezeigt wird:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             x:Class="SkiaSharpFormsDemos.Bitmaps.BitmapRotatorPage"
             Title="Bitmap Rotator">
    <StackLayout>
        <skia:SKCanvasView x:Name="canvasView"
                           VerticalOptions="FillAndExpand"
                           PaintSurface="OnCanvasViewPaintSurface" />

        <Slider x:Name="slider"
                Maximum="360"
                Margin="10, 0"
                ValueChanged="OnSliderValueChanged" />

        <Label Text="{Binding Source={x:Reference slider},
                              Path=Value,
                              StringFormat='Rotate by {0:F0}&#x00B0;'}"
               HorizontalTextAlignment="Center" />

    </StackLayout>
</ContentPage>

Die CodeBehind-Datei lädt eine Bitmapressource und speichert sie als statisches schreibgeschütztes Feld mit dem Namen originalBitmap. Die im PaintSurface Handler angezeigte Bitmap ist rotatedBitmap, die anfangs auf originalBitmap:

public partial class BitmapRotatorPage : ContentPage
{
    static readonly SKBitmap originalBitmap =
        BitmapExtensions.LoadBitmapResource(typeof(BitmapRotatorPage),
            "SkiaSharpFormsDemos.Media.Banana.jpg");

    SKBitmap rotatedBitmap = originalBitmap;

    public BitmapRotatorPage ()
    {
        InitializeComponent ();
    }

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

        canvas.Clear();
        canvas.DrawBitmap(rotatedBitmap, info.Rect, BitmapStretch.Uniform);
    }

    void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
    {
        double angle = args.NewValue;
        double radians = Math.PI * angle / 180;
        float sine = (float)Math.Abs(Math.Sin(radians));
        float cosine = (float)Math.Abs(Math.Cos(radians));
        int originalWidth = originalBitmap.Width;
        int originalHeight = originalBitmap.Height;
        int rotatedWidth = (int)(cosine * originalWidth + sine * originalHeight);
        int rotatedHeight = (int)(cosine * originalHeight + sine * originalWidth);

        rotatedBitmap = new SKBitmap(rotatedWidth, rotatedHeight);

        using (SKCanvas canvas = new SKCanvas(rotatedBitmap))
        {
            canvas.Clear(SKColors.LightPink);
            canvas.Translate(rotatedWidth / 2, rotatedHeight / 2);
            canvas.RotateDegrees((float)angle);
            canvas.Translate(-originalWidth / 2, -originalHeight / 2);
            canvas.DrawBitmap(originalBitmap, new SKPoint());
        }

        canvasView.InvalidateSurface();
    }
}

Der ValueChanged Handler des Slider Vorgangs führt die Vorgänge aus, die basierend auf dem Drehwinkel ein neues rotatedBitmap erstellen. Die neue Breite und Höhe basieren auf absoluten Werten von Sünden und Kosinen der ursprünglichen Breite und Höhe. Die Transformationen, die zum Zeichnen der ursprünglichen Bitmap auf der gedrehten Bitmap verwendet werden, verschieben die ursprüngliche Bitmapmitte an den Ursprung, drehen sie dann um die angegebene Gradzahl, und übersetzen Sie diese Mitte in die Mitte der gedrehten Bitmap. (Die Translate Methoden RotateDegrees und Methoden werden in der entgegengesetzten Reihenfolge aufgerufen als die Anwendung.)

Beachten Sie die Verwendung der Clear Methode, um den Hintergrund eines hell rosafarbenen Hintergrunds rotatedBitmap zu machen. Dies ist ausschließlich das Veranschaulichen der rotatedBitmap Größe auf dem Display:

Bitmap-Rotator

Die gedrehte Bitmap ist nur groß genug, um die gesamte ursprüngliche Bitmap einzuschließen, aber nicht größer.

Kippen von Bitmaps

Ein anderer Vorgang, der häufig für Bitmaps ausgeführt wird, wird als Flipping bezeichnet. Konzeptionell wird die Bitmap in drei Dimensionen um eine vertikale Achse oder horizontale Achse durch die Mitte der Bitmap gedreht. Durch vertikales Kippen wird ein Spiegel Bild erstellt.

Die Bitmap Flipper-Seite in der Beispielanwendung veranschaulicht diese Prozesse. Die XAML-Datei enthält eine SKCanvasView und zwei Schaltflächen zum vertikalen und horizontalen Kippen:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             x:Class="SkiaSharpFormsDemos.Bitmaps.BitmapFlipperPage"
             Title="Bitmap Flipper">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

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

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

        <Button Text="Flip Vertical"
                Grid.Row="1" Grid.Column="0"
                Margin="0, 10"
                Clicked="OnFlipVerticalClicked" />

        <Button Text="Flip Horizontal"
                Grid.Row="1" Grid.Column="1"
                Margin="0, 10"
                Clicked="OnFlipHorizontalClicked" />
    </Grid>
</ContentPage>

Die CodeBehind-Datei implementiert diese beiden Vorgänge in den Clicked Handlern für die Schaltflächen:

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

    public BitmapFlipperPage()
    {
        InitializeComponent();
    }

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

        canvas.Clear();
        canvas.DrawBitmap(bitmap, info.Rect, BitmapStretch.Uniform);
    }

    void OnFlipVerticalClicked(object sender, ValueChangedEventArgs args)
    {
        SKBitmap flippedBitmap = new SKBitmap(bitmap.Width, bitmap.Height);

        using (SKCanvas canvas = new SKCanvas(flippedBitmap))
        {
            canvas.Clear();
            canvas.Scale(-1, 1, bitmap.Width / 2, 0);
            canvas.DrawBitmap(bitmap, new SKPoint());
        }

        bitmap = flippedBitmap;
        canvasView.InvalidateSurface();
    }

    void OnFlipHorizontalClicked(object sender, ValueChangedEventArgs args)
    {
        SKBitmap flippedBitmap = new SKBitmap(bitmap.Width, bitmap.Height);

        using (SKCanvas canvas = new SKCanvas(flippedBitmap))
        {
            canvas.Clear();
            canvas.Scale(1, -1, 0, bitmap.Height / 2);
            canvas.DrawBitmap(bitmap, new SKPoint());
        }

        bitmap = flippedBitmap;
        canvasView.InvalidateSurface();
    }
}

Der vertikale Flip wird durch eine Skalierungstransformation mit einem horizontalen Skalierungsfaktor von –1 erreicht. Die Skalierungsmitte ist die vertikale Mitte der Bitmap. Der horizontale Flip ist eine Skalierungstransformation mit einem vertikalen Skalierungsfaktor von –1.

Wie Sie aus dem umgekehrten Buchstaben auf dem Affenhemd sehen können, ist das Kippen nicht mit der Drehung identisch. Wie der UWP-Screenshot auf der rechten Seite zeigt, entspricht das Kippen sowohl horizontal als auch vertikal dem Drehen von 180 Grad:

Bitmap Flipper

Eine weitere gängige Aufgabe, die mit ähnlichen Techniken behandelt werden kann, ist das Zuschneiden einer Bitmap in eine rechteckige Teilmenge. Dies wird im nächsten Artikel "Zuschneiden von SkiaSharp Bitmaps" beschrieben.