Udostępnij za pośrednictwem


Tworzenie i rysowanie na mapach bitowych SkiaSharp

Wiesz już, jak aplikacja może ładować mapy bitowe z internetu, z zasobów aplikacji i z biblioteki zdjęć użytkownika. Istnieje również możliwość utworzenia nowych map bitowych w aplikacji. Najprostsze podejście obejmuje jeden z konstruktorów klasy SKBitmap:

SKBitmap bitmap = new SKBitmap(width, height);

Parametry width i height są liczbami całkowitymi i określają wymiary pikseli mapy bitowej. Ten konstruktor tworzy pełną mapę bitową z czterema bajtami na piksel: jeden bajt dla składników czerwonego, zielonego, niebieskiego i alfa (nieprzezroczystości).

Po utworzeniu nowej mapy bitowej musisz uzyskać coś na powierzchni mapy bitowej. Zazwyczaj można to zrobić na jeden z dwóch sposobów:

  • Rysuj na mapie bitowej przy użyciu standardowych Canvas metod rysowania.
  • Uzyskaj bezpośredni dostęp do bitów pikseli.

W tym artykule przedstawiono pierwsze podejście:

Przykład rysunku

Drugie podejście zostało omówione w artykule Accessing SkiaSharp Bitmap Pixel (Uzyskiwanie dostępu do pikseli mapy bitowej SkiaSharp).

Rysowanie na mapie bitowej

Rysunek na powierzchni mapy bitowej jest taki sam jak rysunek na ekranie wideo. Aby rysować na ekranie wideo, uzyskujesz SKCanvas obiekt z PaintSurface argumentów zdarzenia. Aby rysować na mapie bitowej, należy utworzyć SKCanvas obiekt przy użyciu konstruktora SKCanvas :

SKCanvas canvas = new SKCanvas(bitmap);

Po zakończeniu rysowania na mapie bitowej można usunąć SKCanvas obiekt. Z tego powodu SKCanvas konstruktor jest zwykle wywoływany w instrukcji using :

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

Następnie można wyświetlić mapę bitową. W późniejszym czasie program może utworzyć nowy SKCanvas obiekt na podstawie tej samej mapy bitowej i rysować na nim trochę więcej.

Strona Hello Bitmap w przykładowej aplikacji zapisuje tekst "Hello, Bitmap!" na mapie bitowej, a następnie wyświetla ten mapa bitowa wiele razy.

Konstruktor obiektu HelloBitmapPage rozpoczyna się od utworzenia SKPaint obiektu do wyświetlania tekstu. Określa wymiary ciągu tekstowego i tworzy mapę bitową z tymi wymiarami. Następnie tworzy obiekt oparty na tej mapie bitowej, wywołuje metodę SKCanvas Clear, a następnie wywołuje metodę DrawText. Zawsze dobrym pomysłem jest wywołanie Clear nowej mapy bitowej, ponieważ nowo utworzona mapa bitowa może zawierać dane losowe.

Konstruktor kończy, tworząc SKCanvasView obiekt do wyświetlania mapy bitowej:

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);
            }
    }
}

Program PaintSurface obsługi renderuje mapę bitową wiele razy w wierszach i kolumnach wyświetlacza. Zwróć uwagę, że Clear metoda w procedurze PaintSurface obsługi ma argument SKColors.Aqua, który koloruje tło powierzchni wyświetlania:

Witaj, Mapa bitowa!

Wygląd tła wodnego ujawnia, że mapa bitowa jest przezroczysta z wyjątkiem tekstu.

Czyszczenie i przejrzystość

Na ekranie strony Witaj mapa bitowa pokazuje, że mapa bitowa utworzona przez program jest przezroczysta z wyjątkiem czarnego tekstu. Dlatego kolor wodny powierzchni wyświetlacza pokazuje przez.

Dokumentacja Clear metod opisanych SKCanvas w instrukcji: "Zastępuje wszystkie piksele w bieżącym klipie kanwy". Użycie słowa "replaces" ujawnia ważną cechę tych metod: Wszystkie metody SKCanvas rysowania dodają coś do istniejącej powierzchni wyświetlania. Metody Clear zastępują już to, co już istnieje.

Clear istnieje w dwóch różnych wersjach:

  • Metoda Clear z parametrem SKColor zastępuje piksele powierzchni wyświetlania pikselami tego koloru.

  • Metoda Clear bez parametrów zastępuje piksele kolorem SKColors.Empty , który jest kolorem, w którym wszystkie składniki (czerwony, zielony, niebieski i alfa) są ustawione na zero. Ten kolor jest czasami określany jako "przezroczysty czarny".

Wywołanie Clear bez argumentów na nowej mapie bitowej inicjuje całą mapę bitową, aby było całkowicie przezroczyste. Wszystko następnie rysowane na mapie bitowej będzie zwykle nieprzezroczyste lub częściowo nieprzezroczyste.

Oto coś do wypróbowania: na stronie Witaj mapa bitowa zastąp Clear metodę zastosowaną bitmapCanvas do tej metody:

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

Kolejność parametrów konstruktora SKColor to czerwony, zielony, niebieski i alfa, gdzie każda wartość może wahać się od 0 do 255. Należy pamiętać, że wartość alfa 0 jest przezroczysta, a wartość alfa 255 jest nieprzezroczysta.

Wartość (255, 0, 0, 128) czyści piksele mapy bitowej na czerwone piksele z nieprzezroczystością 50%. Oznacza to, że tło mapy bitowej jest półprzezroczyste. Półprzezroczyste czerwone tło mapy bitowej łączy się z wodnym tłem powierzchni wyświetlacza, aby utworzyć szare tło.

Spróbuj ustawić kolor tekstu na przezroczysty czarny, umieszczając następujące przypisanie w inicjatorze SKPaint :

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

Można pomyśleć, że ten przezroczysty tekst stworzy w pełni przezroczyste obszary mapy bitowej, za pomocą której zobaczysz tło wodne powierzchni ekranu. Ale tak nie jest. Tekst jest rysowany na podstawie tego, co już znajduje się na mapie bitowej. Przezroczysty tekst nie będzie w ogóle widoczny.

Żadna metoda nigdy nie Draw sprawia, że mapa bitowa jest bardziej przezroczysta. Tylko Clear to może zrobić.

Typy kolorów mapy bitowej

Najprostszy SKBitmap konstruktor umożliwia określenie szerokości i wysokości pikseli całkowitej dla mapy bitowej. Inne SKBitmap konstruktory są bardziej złożone. Te konstruktory wymagają argumentów dwóch typów wyliczenia: SKColorType i SKAlphaType. Inne konstruktory używają SKImageInfo struktury, która konsoliduje te informacje.

Wyliczenie SKColorType ma 9 członków. Każdy z tych elementów członkowskich opisuje konkretny sposób przechowywania pikseli mapy bitowej:

  • Unknown
  • Alpha8 — każdy piksel ma 8 bitów, reprezentując wartość alfa z pełnej przezroczystości do pełnej nieprzezroczystości
  • Rgb565 — każdy piksel ma 16 bitów, 5 bitów dla czerwonego i niebieskiego i 6 dla zielonego
  • Argb4444 — każdy piksel ma 16 bitów, 4 każdy dla alfa, czerwony, zielony i niebieski
  • Rgba8888 — każdy piksel ma 32 bity, 8 każdy dla czerwonego, zielonego, niebieskiego i alfa
  • Bgra8888 — każdy piksel ma 32 bity, 8 bitów dla niebieskiego, zielonego, czerwonego i alfa
  • Index8 — każdy piksel ma 8 bitów i reprezentuje indeks w obiekcie SKColorTable
  • Gray8 — każdy piksel to 8 bitów reprezentujący szary odcień od czarnego do białego
  • RgbaF16 — każdy piksel ma 64 bity, z czerwonym, zielonym, niebieskim i alfa w formacie zmiennoprzecinkowym 16-bitowym

Dwa formaty, w których każdy piksel to 32 piksele (4 bajty) są często nazywane formatami pełnokolorowymi . Wiele innych formatów datuje się od godziny, gdy same filmy wideo nie były w stanie uzyskać pełnego koloru. Mapy bitowe o ograniczonym kolorze były odpowiednie dla tych wyświetlaczy i pozwoliły mapom bitowym zajmować mniej miejsca w pamięci.

W dzisiejszych czasach programiści prawie zawsze używają pełnokolorowych map bitowych i nie przeszkadzają w innych formatach. Wyjątkiem jest RgbaF16 format, który umożliwia większą rozdzielczość kolorów niż nawet formaty pełnokolorowe. Jednak ten format jest używany do celów wyspecjalizowanych, takich jak obrazowanie medyczne, i nie ma większego sensu, gdy jest używany z standardowymi wyświetlaczami pełnokolorowymi.

Ta seria artykułów ogranicza się do SKBitmap formatów kolorów używanych domyślnie, gdy nie określono żadnego SKColorType elementu członkowskiego. Ten format domyślny jest oparty na podstawowej platformie. W przypadku platform obsługiwanych przez Xamarin.Formsprogram domyślnym typem koloru jest:

  • Rgba8888 dla systemów iOS i Android
  • Bgra8888 dla platformy UWP

Jedyną różnicą jest kolejność 4 bajtów w pamięci i staje się to problemem tylko wtedy, gdy uzyskujesz bezpośredni dostęp do bitów pikseli. Nie stanie się to ważne, dopóki nie zostanie wyświetlony artykuł Uzyskiwanie dostępu do pikseli mapy bitowej SkiaSharp.

Wyliczenie SKAlphaType ma cztery elementy członkowskie:

  • Unknown
  • Opaque — mapa bitowa nie ma przezroczystości
  • Premul — składniki kolorów są wstępnie mnożone przez składnik alfa
  • Unpremul — składniki kolorów nie są wstępnie mnożone przez składnik alfa

Oto 4-bajtowy czerwony piksel mapy bitowej z przezroczystością 50% z bajtami wyświetlanymi w kolejności czerwonej, zielonej, niebieskiej, alfa:

0xFF 0x00 0x00 0x80

Gdy mapa bitowa zawierająca półprzezroczyste piksele jest renderowana na powierzchni ekranu, składniki kolorów każdego piksela mapy bitowej muszą być mnożone przez wartość alfa tego piksela, a składniki kolorów odpowiadającego piksela powierzchni wyświetlania muszą być mnożone przez 255 minus wartość alfa. Następnie można połączyć dwa piksele. Mapa bitowa może być renderowana szybciej, jeśli składniki kolorów w pikselach mapy bitowej zostały już wstępnie wyciszone przez wartość alfa. Ten sam czerwony piksel będzie przechowywany w formacie wstępnie pomnożonym:

0x80 0x00 0x00 0x80

Ta poprawa wydajności polega na tym, że SkiaSharp mapy bitowe są domyślnie tworzone w Premul formacie. Ale znowu staje się konieczne, aby wiedzieć to tylko wtedy, gdy uzyskujesz dostęp do bitów pikseli i manipulujesz nimi.

Rysowanie na istniejących mapach bitowych

Nie trzeba tworzyć nowej mapy bitowej, aby ją rysować. Możesz również rysować na istniejącej mapie bitowej.

Strona Monkey Moustache używa swojego konstruktora do załadowania obrazu MonkeyFace.png . Następnie tworzy SKCanvas obiekt oparty na tej mapie bitowej i używa SKPaint obiektów i SKPath do narysowania na nim wąsu:

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);
    }
}

Konstruktor kończy się utworzeniem SKCanvasView programu obsługi, którego PaintSurface program obsługi po prostu wyświetla wynik:

Małpa wąs

Kopiowanie i modyfikowanie map bitowych

Metody SKCanvas , których można użyć do rysowania na mapie bitowej, obejmują DrawBitmap. Oznacza to, że można narysować jedną mapę bitową na innej, zwykle modyfikując ją w jakiś sposób.

Najbardziej wszechstronnym sposobem modyfikowania mapy bitowej jest uzyskiwanie dostępu do rzeczywistych bitów pikseli, temat omówiony w artykule Uzyskiwanie dostępu do pikseli mapy bitowej SkiaSharp. Istnieje jednak wiele innych technik modyfikowania map bitowych, które nie wymagają dostępu do bitów pikseli.

Poniższa mapa bitowa dołączona do przykładowej aplikacji ma szerokość 360 pikseli i 480 pikseli wysokości:

Wspinacze górskie

Załóżmy, że nie otrzymałeś zgody małpy po lewej stronie, aby opublikować to zdjęcie. Jednym z rozwiązań jest ukrycie twarzy małpy przy użyciu techniki nazywanej pixelizacją. Piksele twarzy są zastępowane blokami koloru, dzięki czemu nie można wymyślić funkcji. Bloki koloru są zwykle uzyskiwane z oryginalnego obrazu przez uśrednianie kolorów pikseli odpowiadających tym blokom. Ale nie musisz wykonywać tej średniej samodzielnie. Dzieje się to automatycznie po skopiowaniu mapy bitowej do mniejszego wymiaru pikseli.

Lewa twarz małpy zajmuje około 72-pikselowy obszar kwadratowy z lewym górnym rogu w punkcie (112, 238). Zastąpmy ten 72-pikselowy obszar kwadratowy tablicą 9-do-9 kolorowych bloków, z których każdy ma kwadrat 8 do 8 pikseli.

Strona Pixelize Image ładuje się w tej mapie bitowej i najpierw tworzy małą 9-pikselową kwadratową mapę bitową o nazwie faceBitmap. Jest to miejsce docelowe kopiowania tylko twarzy małpy. Prostokąt docelowy ma tylko 9 pikseli kwadratu, ale prostokąt źródłowy ma kwadrat o powierzchni 72 pikseli. Każdy blok 8-do-8 pikseli źródłowych jest konsolidowany do zaledwie jednego piksela przez uśrednianie kolorów.

Następnym krokiem jest skopiowanie oryginalnej mapy bitowej do nowej mapy bitowej o tym samym rozmiarze o pixelizedBitmapnazwie . Malutka faceBitmap jest następnie kopiowana na wierzchu z prostokątem docelowym o rozmiarze 72 pikseli, dzięki czemu każdy piksel faceBitmap jest rozszerzany do 8 razy większy:

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);
    }
}

Konstruktor kończy się utworzeniem obiektu SKCanvasView , aby wyświetlić wynik:

Pikselowanie obrazu

Obracanie map bitowych

Innym typowym zadaniem jest obracanie map bitowych. Jest to szczególnie przydatne podczas pobierania map bitowych z biblioteki zdjęć i Telefon lub iPad. Jeśli urządzenie nie zostało zatrzymane w określonej orientacji, gdy zdjęcie zostało zrobione, obraz prawdopodobnie będzie do góry nogami lub w bok.

Obracanie mapy bitowej do góry nogami wymaga utworzenia innej mapy bitowej o taki sam rozmiar jak pierwszy, a następnie ustawienie przekształcenia w celu obracania o 180 stopni podczas kopiowania pierwszego do drugiego. We wszystkich przykładach w tej sekcji znajduje SKBitmap się obiekt, bitmap który należy obrócić:

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());
}

Podczas obracania o 90 stopni należy utworzyć mapę bitową o innym rozmiarze niż oryginalny, zamieniając wysokość i szerokość. Jeśli na przykład oryginalna mapa bitowa ma szerokość 1200 pikseli i 800 pikseli, obrócona mapa bitowa ma szerokość 800 pikseli i szerokość 1200 pikseli. Ustaw tłumaczenie i rotację, aby mapa bitowa została obrócona wokół lewego górnego rogu, a następnie przesunięta w widok. (Pamiętaj, że Translate metody i RotateDegrees są wywoływane w odwrotnej kolejności sposobu ich stosowania). Oto kod obracania 90 stopni zgodnie z ruchem wskazówek zegara:

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());
}

A oto podobna funkcja obracania 90 stopni w kierunku wskazówek zegara:

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());
}

Te dwie metody są używane na stronach Photo Puzzle opisanych w artykule Cropping SkiaSharp Bitmaps.

Program, który umożliwia użytkownikowi obracanie mapy bitowej w 90-stopniowych przyrostach, wymaga zaimplementowania tylko jednej funkcji do rotacji o 90 stopni. Użytkownik może następnie obracać się w dowolnej inkrementacji 90 stopni, powtarzając wykonanie tej funkcji.

Program może również obracać mapę bitową o dowolną ilość. Jedną z prostych metod jest zmodyfikowanie funkcji, która obraca się o 180 stopni, zastępując wartość 180 zmienną uogólnioną angle :

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());
}

Jednak w ogólnym przypadku ta logika przycina narożniki obróconej mapy bitowej. Lepszym rozwiązaniem jest obliczenie rozmiaru obróconej mapy bitowej przy użyciu trygonometrii w celu uwzględnienia tych narożników.

Ta trygonometria jest wyświetlana na stronie Rottor mapy bitowej. Plik XAML tworzy wystąpienie elementu SKCanvasView i , Slider który może wahać się od 0 do 360 stopni z wyświetloną bieżącą wartością Label :

<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>

Plik z kodem ładuje zasób mapy bitowej i zapisuje go jako statyczne pole tylko do odczytu o nazwie originalBitmap. Mapa bitowa wyświetlana w procedurze PaintSurface obsługi to rotatedBitmap, która jest początkowo ustawiona na wartość 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();
    }
}

Procedura ValueChanged obsługi Slider wykonuje operacje, które tworzą nowe rotatedBitmap na podstawie kąta obrotu. Nowa szerokość i wysokość są oparte na wartościach bezwzględnych sinusów i cosinusów oryginalnych szerokości i wysokości. Przekształcenia używane do rysowania oryginalnej mapy bitowej na obróconej mapie bitowej przenoszą oryginalny środek mapy bitowej do źródła, a następnie obracają ją według określonej liczby stopni, a następnie przetłumaczą ten środek na środek obróconej mapy bitowej. (Metody Translate i RotateDegrees są wywoływane w odwrotnej kolejności niż sposób ich stosowania).

Zwróć uwagę na użycie Clear metody , aby tło rotatedBitmap jasnoróżowe. Ma to na celu wyłącznie zilustrowanie rozmiaru rotatedBitmap na wyświetlaczu:

Obracak mapy bitowej

Obrócona mapa bitowa jest wystarczająco duża, aby uwzględnić całą oryginalną mapę bitową, ale nie większą.

Przerzucanie map bitowych

Inna operacja często wykonywana na mapach bitowych jest nazywana przerzucaniem. Koncepcyjnie mapa bitowa jest obracana w trzech wymiarach wokół osi pionowej lub osi poziomej przez środek mapy bitowej. Przerzucanie pionowe powoduje utworzenie obrazu dublowanego.

Strona Flipper mapy bitowej w przykładowej aplikacji demonstruje te procesy. Plik XAML zawiera dwa SKCanvasView przyciski i umożliwiające przerzucanie w pionie i w poziomie:

<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>

Plik związany z kodem implementuje te dwie operacje w Clicked programach obsługi przycisków:

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();
    }
}

Przerzucanie w pionie jest realizowane przez przekształcenie skalowania z współczynnikiem skalowania w poziomie –1. Środek skalowania to pionowy środek mapy bitowej. Przerzucanie w poziomie to przekształcenie skalowania z pionowym współczynnikiem skalowania –1.

Jak widać z odwróconej litery na koszuli małpy, przerzucanie nie jest takie samo jak rotacja. Ale jak pokazuje zrzut ekranu platformy UWP po prawej stronie, przerzucanie zarówno w poziomie, jak i w pionie jest takie samo jak obracanie 180 stopni:

Flipper mapy bitowej

Innym typowym zadaniem, które można obsłużyć przy użyciu podobnych technik, jest przycinanie mapy bitowej do prostokątnego podzestawu. Jest to opisane w następnym artykule Przycinanie map bitowych SkiaSharp.