Udostępnij za pośrednictwem


Gradient liniowy SkiaSharp

Klasa SKPaint definiuje Color właściwość, która jest używana do pociągnięcia linii lub wypełniania obszarów kolorem stałym. Alternatywnie można pociągnięć linie lub wypełnić obszary gradientami, które są stopniowymi mieszankami kolorów:

Próbka gradientu liniowego

Najbardziej podstawowym typem gradientu jest gradient liniowy. Połączenie kolorów występuje na linii (nazywanej linią gradientu) od jednego punktu do drugiego. Linie prostopadłe do linii gradientu mają ten sam kolor. Gradient liniowy tworzy się przy użyciu jednej z dwóch metod statycznych SKShader.CreateLinearGradient . Różnica między dwoma przeciążeniami polega na tym, że jeden zawiera przekształcenie macierzy, a drugi nie.

Te metody zwracają obiekt typu SKShader , który został ustawiony na Shader właściwość SKPaint. Shader Jeśli właściwość ma wartość inną niż null, zastępuje Color właściwość . Każda linia, która jest pociągnięta lub dowolny obszar wypełniony przy użyciu tego SKPaint obiektu, jest oparta na gradientzie, a nie na kolorze stałym.

Uwaga

Właściwość Shader jest ignorowana podczas dołączania SKPaint obiektu do wywołania DrawBitmap . Możesz użyć Color właściwości SKPaint , aby ustawić poziom przezroczystości do wyświetlania mapy bitowej (zgodnie z opisem w artykule Wyświetlanie map bitowych SkiaSharp), ale nie można użyć Shader właściwości do wyświetlania mapy bitowej z przezroczystością gradientu. Inne techniki są dostępne do wyświetlania map bitowych z transparencjami gradientu: są one opisane w artykułach SkiaSharp cykliczne gradienty i komposiowanie SkiaSharp i tryby mieszania.

Gradienty narożne

Często gradient liniowy rozciąga się od jednego rogu prostokąta do drugiego. Jeśli punkt początkowy to lewy górny róg prostokąta, gradient może rozszerzyć:

  • w pionie do lewego dolnego rogu
  • w prawym górnym rogu
  • po przekątnej do prawego dolnego rogu

Gradient liniowy ukośny jest przedstawiony na pierwszej stronie w sekcji Cieniowania SkiaSharp i Inne efekty próbki. Na stronie Gradient narożny zostanie utworzony SKCanvasView obiekt w konstruktorze. Procedura PaintSurface obsługi tworzy SKPaint obiekt w instrukcji using , a następnie definiuje prostokąt o powierzchni 300 pikseli wyśrodkowany na kanwie:

public class CornerToCornerGradientPage : ContentPage
{
    ···
    public CornerToCornerGradientPage ()
    {
        Title = "Corner-to-Corner Gradient";

        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 300-pixel square centered rectangle
            float x = (info.Width - 300) / 2;
            float y = (info.Height - 300) / 2;
            SKRect rect = new SKRect(x, y, x + 300, y + 300);

            // Create linear gradient from upper-left to lower-right
            paint.Shader = SKShader.CreateLinearGradient(
                                new SKPoint(rect.Left, rect.Top),
                                new SKPoint(rect.Right, rect.Bottom),
                                new SKColor[] { SKColors.Red, SKColors.Blue },
                                new float[] { 0, 1 },
                                SKShaderTileMode.Repeat);

            // Draw the gradient on the rectangle
            canvas.DrawRect(rect, paint);
            ···
        }
    }
}

Właściwość Shader SKPaint właściwości jest przypisywana wartość zwracana SKShader z metody statycznej SKShader.CreateLinearGradient . Pięć argumentów jest następujących:

  • Punkt początkowy gradientu ustawiony tutaj na lewy górny róg prostokąta
  • Punkt końcowy gradientu ustawiony tutaj na prawy dolny róg prostokąta
  • Tablica dwóch lub większej liczby kolorów, które przyczyniają się do gradientu
  • Tablica float wartości wskazująca względną pozycję kolorów w linii gradientu
  • Element członkowski SKShaderTileMode wyliczenia wskazujący, jak gradient zachowuje się poza końcami linii gradientu

Po utworzeniu DrawRect obiektu gradientowego metoda rysuje prostokąt o powierzchni 300 pikseli przy użyciu SKPaint obiektu zawierającego cieniowanie. W tym miejscu działa w systemach iOS, Android i platforma uniwersalna systemu Windows (UWP):

Gradient narożny

Linia gradientu jest definiowana przez dwa punkty określone jako dwa pierwsze argumenty. Zwróć uwagę, że te punkty są względne względem kanwy , a nie do obiektu graficznego wyświetlanego z gradientem. Wzdłuż linii gradientu kolor stopniowo przechodzi od czerwonego w lewym górnym rogu do niebieskiego w prawym dolnym rogu. Każda linia, która jest prostopadła do linii gradientu, ma stały kolor.

Tablica float wartości określonych jako czwarty argument ma korespondencję jeden do jednego z tablicą kolorów. Wartości wskazują położenie względne wzdłuż linii gradientu, w której występują te kolory. W tym miejscu wartość 0 oznacza, że Red występuje na początku linii gradientu, a 1 oznacza, że Blue występuje na końcu linii. Liczby muszą być rosnące i powinny należeć do zakresu od 0 do 1. Jeśli nie są w tym zakresie, zostaną one dostosowane tak, aby mieściły się w tym zakresie.

Dwie wartości w tablicy można ustawić na wartość inną niż 0 i 1. Wypróbuj te metody:

new float[] { 0.25f, 0.75f }

Teraz cały pierwszy kwartał linii gradientu jest czysty czerwony, a ostatni kwartał jest czysty niebieski. Mieszanka czerwonego i niebieskiego jest ograniczona do środkowej połowy linii gradientu.

Ogólnie rzecz biorąc, należy umieścić te wartości w pozycji równej od 0 do 1. Jeśli tak jest, możesz po prostu podać null jako czwarty argument na CreateLinearGradient.

Mimo że ten gradient jest definiowany między dwoma rogami prostokąta 300 pikseli kwadratowych, nie ogranicza się do wypełnienia tego prostokąta. Na stronie Gradient narożny znajduje się dodatkowy kod, który reaguje na naciśnięcia lub kliknięcia myszy na stronie. Pole drawBackground jest przełączane między true i false przy każdym naciśnięciu. Jeśli wartość to true, PaintSurface program obsługi używa tego samego SKPaint obiektu do wypełnienia całej kanwy, a następnie rysuje czarny prostokąt wskazujący mniejszy prostokąt:

public class CornerToCornerGradientPage : ContentPage
{
    bool drawBackground;

    public CornerToCornerGradientPage ()
    {
        ···
        TapGestureRecognizer tap = new TapGestureRecognizer();
        tap.Tapped += (sender, args) =>
        {
            drawBackground ^= true;
            canvasView.InvalidateSurface();
        };
        canvasView.GestureRecognizers.Add(tap);
    }

    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        ···
        using (SKPaint paint = new SKPaint())
        {
            ···
            if (drawBackground)
            {
                // Draw the gradient on the whole canvas
                canvas.DrawRect(info.Rect, paint);

                // Outline the smaller rectangle
                paint.Shader = null;
                paint.Style = SKPaintStyle.Stroke;
                paint.Color = SKColors.Black;
                canvas.DrawRect(rect, paint);
            }
        }
    }
}

Oto, co zobaczysz po naciśnięciu ekranu:

Gradient narożny —pełny

Zwróć uwagę, że gradient powtarza się w tym samym wzorcu poza punktami definiującymi linię gradientu. To powtórzenie występuje, ponieważ ostatnim argumentem CreateLinearGradient jest SKShaderTileMode.Repeat. (Wkrótce zobaczysz inne opcje).

Zwróć również uwagę, że punkty używane do określenia linii gradientu nie są unikatowe. Linie, które są prostopadłe do linii gradientu mają ten sam kolor, więc istnieje nieskończona liczba linii gradientu, które można określić dla tego samego efektu. Na przykład podczas wypełniania prostokąta gradientem poziomym można określić lewe i prawe górne rogi albo lewe i dolne i prawe dolne rogi albo dowolne dwa punkty, które znajdują się nawet z tymi liniami i równolegle do nich.

Interakcyjny eksperyment

Możesz interakcyjnie eksperymentować z gradientami liniowymi na stronie Interakcyjny gradient liniowy . Ta strona używa klasy wprowadzonej InteractivePage w artykule Trzy sposoby rysowania łuku. InteractivePage Obsługuje TouchEffect zdarzenia w celu utrzymania kolekcji TouchPoint obiektów, które można przenosić palcami lub myszą.

Plik XAML dołącza TouchEffect element do elementu nadrzędnego elementu SKCanvasView , a także element Picker , który umożliwia wybranie jednego z trzech elementów SKShaderTileMode członkowskich wyliczenia:

<local:InteractivePage xmlns="http://xamarin.com/schemas/2014/forms"
                       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                       xmlns:local="clr-namespace:SkiaSharpFormsDemos"
                       xmlns:skia="clr-namespace:SkiaSharp;assembly=SkiaSharp"
                       xmlns:skiaforms="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
                       xmlns:tt="clr-namespace:TouchTracking"
                       x:Class="SkiaSharpFormsDemos.Effects.InteractiveLinearGradientPage"
                       Title="Interactive Linear Gradient">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <Grid BackgroundColor="White"
              Grid.Row="0">
            <skiaforms:SKCanvasView x:Name="canvasView"
                                    PaintSurface="OnCanvasViewPaintSurface" />
            <Grid.Effects>
                <tt:TouchEffect Capture="True"
                                TouchAction="OnTouchEffectAction" />
            </Grid.Effects>
        </Grid>

        <Picker x:Name="tileModePicker"
                Grid.Row="1"
                Title="Shader Tile Mode"
                Margin="10"
                SelectedIndexChanged="OnPickerSelectedIndexChanged">
            <Picker.ItemsSource>
                <x:Array 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>
            </Picker.ItemsSource>

            <Picker.SelectedIndex>
                0
            </Picker.SelectedIndex>
        </Picker>
    </Grid>
</local:InteractivePage>

Konstruktor w pliku za kodem tworzy dwa TouchPoint obiekty dla punktów początkowych i końcowych gradientu liniowego. Procedura PaintSurface obsługi definiuje tablicę trzech kolorów (dla gradientu od czerwonego do zielonego na niebieski) i uzyskuje prąd SKShaderTileMode z elementu Picker:

public partial class InteractiveLinearGradientPage : InteractivePage
{
    public InteractiveLinearGradientPage ()
    {
        InitializeComponent ();

        touchPoints = new TouchPoint[2];

        for (int i = 0; i < 2; i++)
        {
            touchPoints[i] = new TouchPoint
            {
                Center = new SKPoint(100 + i * 200, 100 + i * 200)
            };
        }

        InitializeComponent();
        baseCanvasView = canvasView;
    }

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

        SKColor[] colors = { SKColors.Red, SKColors.Green, SKColors.Blue };
        SKShaderTileMode tileMode =
            (SKShaderTileMode)(tileModePicker.SelectedIndex == -1 ?
                                        0 : tileModePicker.SelectedItem);

        using (SKPaint paint = new SKPaint())
        {
            paint.Shader = SKShader.CreateLinearGradient(touchPoints[0].Center,
                                                         touchPoints[1].Center,
                                                         colors,
                                                         null,
                                                         tileMode);
            canvas.DrawRect(info.Rect, paint);
        }
        ···
    }
}

Procedura PaintSurface obsługi tworzy SKShader obiekt na podstawie wszystkich tych informacji i używa go do kolorowania całej kanwy. Tablica float wartości jest ustawiona na nullwartość . W przeciwnym razie w celu wyrównania trzech kolorów należy ustawić ten parametr na tablicę z wartościami 0, 0,5 i 1.

Większość PaintSurface procedury obsługi jest poświęcona wyświetlaniu kilku obiektów: punktów dotykowych jako okręgów konturowych, linii gradientu i linii prostopadłych do linii gradientu w punktach dotykowych:

public partial class InteractiveLinearGradientPage : InteractivePage
{
    ···
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        ···
        // Display the touch points here rather than by TouchPoint
        using (SKPaint paint = new SKPaint())
        {
            paint.Style = SKPaintStyle.Stroke;
            paint.Color = SKColors.Black;
            paint.StrokeWidth = 3;

            foreach (TouchPoint touchPoint in touchPoints)
            {
                canvas.DrawCircle(touchPoint.Center, touchPoint.Radius, paint);
            }

            // Draw gradient line connecting touchpoints
            canvas.DrawLine(touchPoints[0].Center, touchPoints[1].Center, paint);

            // Draw lines perpendicular to the gradient line
            SKPoint vector = touchPoints[1].Center - touchPoints[0].Center;
            float length = (float)Math.Sqrt(Math.Pow(vector.X, 2) +
                                            Math.Pow(vector.Y, 2));
            vector.X /= length;
            vector.Y /= length;
            SKPoint rotate90 = new SKPoint(-vector.Y, vector.X);
            rotate90.X *= 200;
            rotate90.Y *= 200;

            canvas.DrawLine(touchPoints[0].Center,
                            touchPoints[0].Center + rotate90,
                            paint);

            canvas.DrawLine(touchPoints[0].Center,
                            touchPoints[0].Center - rotate90,
                            paint);

            canvas.DrawLine(touchPoints[1].Center,
                            touchPoints[1].Center + rotate90,
                            paint);

            canvas.DrawLine(touchPoints[1].Center,
                            touchPoints[1].Center - rotate90,
                            paint);
        }
    }
}

Linia gradientowa łącząca dwa punkty dotykowe jest łatwa do narysowania, ale linie prostopadłe wymagają więcej pracy. Linia gradientu jest konwertowana na wektor, znormalizowany pod kątem długości jednej jednostki, a następnie obracana o 90 stopni. Ten wektor ma wtedy długość 200 pikseli. Służy do rysowania czterech linii, które rozciągają się od punktów dotykowych, aby być prostopadłe do linii gradientu.

Linie prostopadłe pokrywają się z początkiem i końcem gradientu. To, co dzieje się poza tymi wierszami, zależy od ustawienia SKShaderTileMode wyliczenia:

Gradient liniowy interakcyjny

Trzy zrzuty ekranu przedstawiają wyniki trzech różnych wartości elementu SKShaderTileMode. Zrzut ekranu systemu iOS przedstawia SKShaderTileMode.Clampelement , który po prostu rozszerza kolory na obramowanie gradientu. Opcja SKShaderTileMode.Repeat na zrzucie ekranu systemu Android pokazuje, jak wzorzec gradientu jest powtarzany. Opcja SKShaderTileMode.Mirror na zrzucie ekranu platformy uniwersalnej systemu Windows również powtarza wzorzec, ale wzorzec jest odwracany za każdym razem, co nie powoduje przerwania kolorów.

Gradienty na gradientach

Klasa SKShader nie definiuje żadnych właściwości publicznych ani metod z wyjątkiem Dispose. Obiekty SKShader utworzone przez jego metody statyczne są zatem niezmienne. Nawet jeśli używasz tego samego gradientu dla dwóch różnych obiektów, prawdopodobnie zechcesz nieznacznie zmienić gradient. W tym celu należy utworzyć nowy SKShader obiekt.

Na stronie Tekst gradientu jest wyświetlany tekst i pole nawiasu klamrowego, które są kolorowe z podobnymi gradientami:

Tekst gradientowy

Jedynymi różnicami w gradientach są punkty początkowe i końcowe. Gradient używany do wyświetlania tekstu opiera się na dwóch punktach na rogach prostokąta ograniczenia tekstu. W tle dwa punkty są oparte na całej kanwie. Oto kod:

public class GradientTextPage : ContentPage
{
    const string TEXT = "GRADIENT";

    public GradientTextPage ()
    {
        Title = "Gradient Text";

        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 gradient for background
            paint.Shader = SKShader.CreateLinearGradient(
                                new SKPoint(0, 0),
                                new SKPoint(info.Width, info.Height),
                                new SKColor[] { new SKColor(0x40, 0x40, 0x40),
                                                new SKColor(0xC0, 0xC0, 0xC0) },
                                null,
                                SKShaderTileMode.Clamp);

            // Draw background
            canvas.DrawRect(info.Rect, paint);

            // Set TextSize to fill 90% of width
            paint.TextSize = 100;
            float width = paint.MeasureText(TEXT);
            float scale = 0.9f * info.Width / width;
            paint.TextSize *= scale;

            // Get text bounds
            SKRect textBounds = new SKRect();
            paint.MeasureText(TEXT, ref textBounds);

            // Calculate offsets to center the text on the screen
            float xText = info.Width / 2 - textBounds.MidX;
            float yText = info.Height / 2 - textBounds.MidY;

            // Shift textBounds by that amount
            textBounds.Offset(xText, yText);

            // Create gradient for text
            paint.Shader = SKShader.CreateLinearGradient(
                                new SKPoint(textBounds.Left, textBounds.Top),
                                new SKPoint(textBounds.Right, textBounds.Bottom),
                                new SKColor[] { new SKColor(0x40, 0x40, 0x40),
                                                new SKColor(0xC0, 0xC0, 0xC0) },
                                null,
                                SKShaderTileMode.Clamp);

            // Draw text
            canvas.DrawText(TEXT, xText, yText, paint);
        }
    }
}

Właściwość Shader SKPaint obiektu jest ustawiana jako pierwsza, aby wyświetlić gradient do pokrycia tła. Punkty gradientu są ustawione na lewe górne i prawe dolne rogi kanwy.

Kod ustawia TextSize właściwość SKPaint obiektu, tak aby tekst był wyświetlany na poziomie 90% szerokości kanwy. Granice tekstu służą do obliczania xText wartości i yText przekazywania ich do DrawText metody w celu wyśrodkowania tekstu.

Punkty gradientu drugiego CreateLinearGradient wywołania muszą jednak odwoływać się do lewego górnego i prawego dolnego rogu tekstu względem kanwy po wyświetleniu. Jest to realizowane przez przesunięcie prostokąta textBounds według tych samych xText wartości i yText :

textBounds.Offset(xText, yText);

Teraz do ustawiania punktów początkowych i końcowych gradientu można użyć prawego górnego i dolnego rogu prostokąta.

Animowanie gradientu

Istnieje kilka sposobów animowania gradientu. Jednym z podejść jest animowanie punktów początkowych i końcowych. Strona Animacja gradientu przenosi dwa punkty wokół okręgu, który jest wyśrodkowany na kanwie. Promień tego okręgu wynosi połowę szerokości lub wysokości kanwy, w zależności od tego, która z nich jest mniejsza. Punkty początkowe i końcowe znajdują się naprzeciwko siebie w tym okręgu, a gradient przechodzi od białego do czarnego z trybem kafelka Mirror :

Animacja gradientu

Konstruktor tworzy obiekt SKCanvasView. Metody OnAppearing i OnDisappearing obsługują logikę animacji:

public class GradientAnimationPage : ContentPage
{
    SKCanvasView canvasView;
    bool isAnimating;
    double angle;
    Stopwatch stopwatch = new Stopwatch();

    public GradientAnimationPage()
    {
        Title = "Gradient Animation";

        canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;
    }

    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 = 3000;
        angle = 2 * Math.PI * (stopwatch.ElapsedMilliseconds % duration) / duration;
        canvasView.InvalidateSurface();

        return isAnimating;
    }
    ···
}

Metoda OnTimerTick oblicza angle wartość animowaną z zakresu od 0 do 2π co 3 sekundy.

Oto jeden ze sposobów obliczania dwóch punktów gradientu. Wartość SKPoint o nazwie vector jest obliczana w celu rozszerzenia od środka kanwy do punktu na promień okręgu. Kierunek tego wektora jest oparty na sinusie i wartościach cosinus kąta. Następnie obliczane są dwa przeciwległe punkty gradientu: jeden punkt jest obliczany przez odjęcie tego wektora od punktu środkowego, a drugi punkt jest obliczany przez dodanie wektora do punktu środkowego:

public class GradientAnimationPage : 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())
        {
            SKPoint center = new SKPoint(info.Rect.MidX, info.Rect.MidY);
            int radius = Math.Min(info.Width, info.Height) / 2;
            SKPoint vector = new SKPoint((float)(radius * Math.Cos(angle)),
                                         (float)(radius * Math.Sin(angle)));

            paint.Shader = SKShader.CreateLinearGradient(
                                center - vector,
                                center + vector,
                                new SKColor[] { SKColors.White, SKColors.Black },
                                null,
                                SKShaderTileMode.Mirror);

            canvas.DrawRect(info.Rect, paint);
        }
    }
}

Nieco inne podejście wymaga mniej kodu. Takie podejście korzysta z SKShader.CreateLinearGradient metody przeciążenia z przekształceniem macierzy jako ostatnim argumentem. Ta metoda jest wersją w przykładzie:

public class GradientAnimationPage : 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.CreateLinearGradient(
                                new SKPoint(0, 0),
                                info.Width < info.Height ? new SKPoint(info.Width, 0) :
                                                           new SKPoint(0, info.Height),
                                new SKColor[] { SKColors.White, SKColors.Black },
                                new float[] { 0, 1 },
                                SKShaderTileMode.Mirror,
                                SKMatrix.MakeRotation((float)angle, info.Rect.MidX, info.Rect.MidY));

            canvas.DrawRect(info.Rect, paint);
        }
    }
}

Jeśli szerokość kanwy jest mniejsza niż wysokość, dwa punkty gradientu są ustawione na (0, 0) i (info.Width, 0). Przekształcenie obrotu przekazane jako ostatni argument CreateLinearGradient skutecznie obraca te dwa punkty wokół środka ekranu.

Należy pamiętać, że jeśli kąt wynosi 0, nie ma obrotu, a dwa punkty gradientu to lewe górne i prawe górne rogi kanwy. Te punkty nie są tymi samymi punktami gradientu obliczonymi jak pokazano w poprzednim CreateLinearGradient wywołaniu. Punkty te są jednak równoległe do linii gradientu poziomego, która przecina środek kanwy i powoduje taki sam gradient.

Gradient tęczy

Strona Rainbow Gradient rysuje tęczę z lewego górnego rogu kanwy do prawego dolnego rogu. Ale ten gradient tęczy nie jest jak prawdziwa tęcza. Jest prosty, a nie zakrzywiony, ale opiera się na ośmiu kolorach HSL (odcienie nasycenia-jasność), które są określane przez jazdę na rowerze przez wartości odcieni od 0 do 360:

SKColor[] colors = new SKColor[8];

for (int i = 0; i < colors.Length; i++)
{
    colors[i] = SKColor.FromHsl(i * 360f / (colors.Length - 1), 100, 50);
}

Ten kod jest częścią PaintSurface procedury obsługi pokazanej poniżej. Procedura obsługi rozpoczyna się od utworzenia ścieżki definiującej sześciostronny wielokąt, który rozciąga się od lewego górnego rogu kanwy do prawego dolnego rogu:

public class RainbowGradientPage : ContentPage
{
    public RainbowGradientPage ()
    {
        Title = "Rainbow Gradient";

        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 (SKPath path = new SKPath())
        {
            float rainbowWidth = Math.Min(info.Width, info.Height) / 2f;

            // Create path from upper-left to lower-right corner
            path.MoveTo(0, 0);
            path.LineTo(rainbowWidth / 2, 0);
            path.LineTo(info.Width, info.Height - rainbowWidth / 2);
            path.LineTo(info.Width, info.Height);
            path.LineTo(info.Width - rainbowWidth / 2, info.Height);
            path.LineTo(0, rainbowWidth / 2);
            path.Close();

            using (SKPaint paint = new SKPaint())
            {
                SKColor[] colors = new SKColor[8];

                for (int i = 0; i < colors.Length; i++)
                {
                    colors[i] = SKColor.FromHsl(i * 360f / (colors.Length - 1), 100, 50);
                }

                paint.Shader = SKShader.CreateLinearGradient(
                                    new SKPoint(0, rainbowWidth / 2),
                                    new SKPoint(rainbowWidth / 2, 0),
                                    colors,
                                    null,
                                    SKShaderTileMode.Repeat);

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

Dwa punkty gradientu w CreateLinearGradient metodzie są oparte na dwóch punktach definiujących tę ścieżkę: Oba punkty znajdują się blisko lewego górnego rogu. Pierwszy znajduje się na górnej krawędzi kanwy, a drugi znajduje się na lewej krawędzi kanwy. Oto wynik:

Wadliwe gradienty tęczy

Jest to interesujący obraz, ale nie jest to całkiem intencja. Problem polega na tym, że podczas tworzenia gradientu liniowego linie koloru stałego są prostopadłe do linii gradientu. Linia gradientu opiera się na punktach, w których rysunek dotyka lewej i górnej strony, a linia ta zazwyczaj nie jest prostopadła do krawędzi rysunku, które rozciągają się na prawy dolny róg. Takie podejście działałoby tylko wtedy, gdy kanwa była kwadratowa.

Aby utworzyć właściwy gradient tęczy, linia gradientu musi być prostopadła do krawędzi tęczy. Jest to bardziej zaangażowane obliczenie. Należy zdefiniować wektor, który jest równoległy do długiej strony rysunku. Wektor jest obracany o 90 stopni, tak aby był prostopadły do tej strony. Następnie wydłuża się szerokość rysunku przez pomnożenie przez rainbowWidthwartość . Dwa punkty gradientu są obliczane na podstawie punktu po stronie rysunku i tego punktu oraz wektora. Oto kod wyświetlany na stronie Rainbow Gradient w przykładzie:

public class RainbowGradientPage : ContentPage
{
    ···
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        ···
        using (SKPath path = new SKPath())
        {
            ···
            using (SKPaint paint = new SKPaint())
            {
                ···
                // Vector on lower-left edge, from top to bottom
                SKPoint edgeVector = new SKPoint(info.Width - rainbowWidth / 2, info.Height) -
                                     new SKPoint(0, rainbowWidth / 2);

                // Rotate 90 degrees counter-clockwise:
                SKPoint gradientVector = new SKPoint(edgeVector.Y, -edgeVector.X);

                // Normalize
                float length = (float)Math.Sqrt(Math.Pow(gradientVector.X, 2) +
                                                Math.Pow(gradientVector.Y, 2));
                gradientVector.X /= length;
                gradientVector.Y /= length;

                // Make it the width of the rainbow
                gradientVector.X *= rainbowWidth;
                gradientVector.Y *= rainbowWidth;

                // Calculate the two points
                SKPoint point1 = new SKPoint(0, rainbowWidth / 2);
                SKPoint point2 = point1 + gradientVector;

                paint.Shader = SKShader.CreateLinearGradient(point1,
                                                             point2,
                                                             colors,
                                                             null,
                                                             SKShaderTileMode.Repeat);

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

Teraz kolory tęczy są wyrównane do postaci:

Gradient tęczy

Kolory nieskończoności

Gradient tęczy jest również używany na stronie Kolory nieskończoności. Ta strona rysuje znak nieskończoności przy użyciu obiektu ścieżki opisanego w artykule Trzy typy krzywych Béziera. Obraz jest następnie kolorowy animowanym gradientem tęczy, który stale zamiata cały obraz.

Konstruktor tworzy SKPath obiekt opisujący znak nieskończoności. Po utworzeniu ścieżki konstruktor może również uzyskać prostokątne granice ścieżki. Następnie oblicza wartość o nazwie gradientCycleLength. Jeśli gradient jest oparty na lewym górnym i prawym dolnym rogu pathBounds prostokąta, ta gradientCycleLength wartość jest całkowitą szerokością poziomą wzorca gradientu:

public class InfinityColorsPage : ContentPage
{
    ···
    SKCanvasView canvasView;

    // Path information
    SKPath infinityPath;
    SKRect pathBounds;
    float gradientCycleLength;

    // Gradient information
    SKColor[] colors = new SKColor[8];
    ···

    public InfinityColorsPage ()
    {
        Title = "Infinity Colors";

        // Create path for infinity sign
        infinityPath = new SKPath();
        infinityPath.MoveTo(0, 0);                                  // Center
        infinityPath.CubicTo(  50,  -50,   95, -100,  150, -100);   // To top of right loop
        infinityPath.CubicTo( 205, -100,  250,  -55,  250,    0);   // To far right of right loop
        infinityPath.CubicTo( 250,   55,  205,  100,  150,  100);   // To bottom of right loop
        infinityPath.CubicTo(  95,  100,   50,   50,    0,    0);   // Back to center  
        infinityPath.CubicTo( -50,  -50,  -95, -100, -150, -100);   // To top of left loop
        infinityPath.CubicTo(-205, -100, -250,  -55, -250,    0);   // To far left of left loop
        infinityPath.CubicTo(-250,   55, -205,  100, -150,  100);   // To bottom of left loop
        infinityPath.CubicTo( -95,  100, - 50,   50,    0,    0);   // Back to center
        infinityPath.Close();

        // Calculate path information
        pathBounds = infinityPath.Bounds;
        gradientCycleLength = pathBounds.Width +
            pathBounds.Height * pathBounds.Height / pathBounds.Width;

        // Create SKColor array for gradient
        for (int i = 0; i < colors.Length; i++)
        {
            colors[i] = SKColor.FromHsl(i * 360f / (colors.Length - 1), 100, 50);
        }

        canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;
    }
    ···
}

Konstruktor tworzy również tablicę colors dla tęczy i SKCanvasView obiekt.

Przesłonięcia OnAppearing metod i OnDisappearing wykonują obciążenie animacji. Metoda OnTimerTick animuje offset pole z zakresu od 0 do gradientCycleLength co dwie sekundy:

public class InfinityColorsPage : ContentPage
{
    ···
    // For animation
    bool isAnimating;
    float offset;
    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 = 2;     // seconds
        double progress = stopwatch.Elapsed.TotalSeconds % duration / duration;
        offset = (float)(gradientCycleLength * progress);
        canvasView.InvalidateSurface();

        return isAnimating;
    }
    ···
}

PaintSurface Na koniec program obsługi renderuje znak nieskończoności. Ponieważ ścieżka zawiera współrzędne ujemne i dodatnie otaczające punkt środkowy (0, 0), Translate przekształcenie na kanwie jest używane do przesunięcia jej do środka. Przekształcenie tłumaczenia następuje Scale po przekształceniu, które stosuje współczynnik skalowania, który sprawia, że znak nieskończoności jest tak duży, jak to możliwe, przy jednoczesnym zachowaniu w granicach 95% szerokości i wysokości kanwy.

Zwróć uwagę, że STROKE_WIDTH stała jest dodawana do szerokości i wysokości prostokąta ograniczenia ścieżki. Ścieżka zostanie pociągnięta linią tej szerokości, więc rozmiar renderowanej nieskończoności jest zwiększany o połowę tej szerokości na wszystkich czterech stronach:

public class InfinityColorsPage : ContentPage
{
    const int STROKE_WIDTH = 50;
    ···
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        // Set transforms to shift path to center and scale to canvas size
        canvas.Translate(info.Width / 2, info.Height / 2);
        canvas.Scale(0.95f *
            Math.Min(info.Width / (pathBounds.Width + STROKE_WIDTH),
                     info.Height / (pathBounds.Height + STROKE_WIDTH)));

        using (SKPaint paint = new SKPaint())
        {
            paint.Style = SKPaintStyle.Stroke;
            paint.StrokeWidth = STROKE_WIDTH;
            paint.Shader = SKShader.CreateLinearGradient(
                                new SKPoint(pathBounds.Left, pathBounds.Top),
                                new SKPoint(pathBounds.Right, pathBounds.Bottom),
                                colors,
                                null,
                                SKShaderTileMode.Repeat,
                                SKMatrix.MakeTranslation(offset, 0));

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

Przyjrzyj się punktom przekazanym jako dwa pierwsze argumenty elementu SKShader.CreateLinearGradient. Te punkty są oparte na oryginalnym prostokątie ograniczenia ścieżki. Pierwszy punkt to (–250, –100), a drugi to (250, 100). Wewnętrznie do SkiaSharp te punkty są poddawane bieżącej transformacji kanwy, dzięki czemu są one poprawnie wyrównane z wyświetlanym znakiem nieskończoności.

Bez ostatniego argumentu do CreateLinearGradient, zobaczysz gradient tęczy, który rozciąga się od lewego górnego znaku nieskończoności do dolnej prawej. (Gradient rozciąga się od lewego górnego rogu do prawego dolnego rogu prostokąta ograniczenia. Renderowany znak nieskończoności jest większy niż prostokąt ograniczenia o połowę STROKE_WIDTH wartości po wszystkich stronach. Ponieważ gradient jest czerwony zarówno na początku, jak i na końcu, a gradient jest tworzony z wartością SKShaderTileMode.Repeat, różnica nie jest zauważalna.

Z tym ostatnim argumentem do CreateLinearGradient, wzorzec gradientu stale zamiata się na obrazie:

Kolory nieskończoności

Przezroczystość i gradienty

Kolory, które przyczyniają się do gradientu, mogą zawierać przezroczystość. Zamiast gradientu, który zanika z jednego koloru do drugiego, gradient może zniknąć z koloru do przezroczystego.

Możesz użyć tej techniki w celu uzyskania interesujących efektów. Jeden z klasycznych przykładów przedstawia obiekt graficzny z odbiciem:

Gradient Emocje ion

Tekst, który jest do góry nogami, jest kolorowy gradientem, który jest 50% przezroczysty u góry, aby w pełni przezroczysty u dołu. Te poziomy przezroczystości są skojarzone z wartościami alfa 0x80 i 0.

Procedura PaintSurface obsługi na stronie gradientu Emocje ion skaluje rozmiar tekstu do 90% szerokości kanwy. Następnie oblicza xText i yText wartości, aby ustawić tekst do wyśrodkowania w poziomie, ale siedząc na linii bazowej odpowiadającej pionowemu środkowi strony:

public class ReflectionGradientPage : ContentPage
{
    const string TEXT = "Reflection";

    public ReflectionGradientPage ()
    {
        Title = "Reflection Gradient";

        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())
        {
            // Set text color to blue
            paint.Color = SKColors.Blue;

            // Set text size to fill 90% of width
            paint.TextSize = 100;
            float width = paint.MeasureText(TEXT);
            float scale = 0.9f * info.Width / width;
            paint.TextSize *= scale;

            // Get text bounds
            SKRect textBounds = new SKRect();
            paint.MeasureText(TEXT, ref textBounds);

            // Calculate offsets to position text above center
            float xText = info.Width / 2 - textBounds.MidX;
            float yText = info.Height / 2;

            // Draw unreflected text
            canvas.DrawText(TEXT, xText, yText, paint);

            // Shift textBounds to match displayed text
            textBounds.Offset(xText, yText);

            // Use those offsets to create a gradient for the reflected text
            paint.Shader = SKShader.CreateLinearGradient(
                                new SKPoint(0, textBounds.Top),
                                new SKPoint(0, textBounds.Bottom),
                                new SKColor[] { paint.Color.WithAlpha(0),
                                                paint.Color.WithAlpha(0x80) },
                                null,
                                SKShaderTileMode.Clamp);

            // Scale the canvas to flip upside-down around the vertical center
            canvas.Scale(1, -1, 0, yText);

            // Draw reflected text
            canvas.DrawText(TEXT, xText, yText, paint);
        }
    }
}

Te xText wartości i yText są tymi samymi wartościami używanymi do wyświetlania odzwierciedlonego tekstu w DrawText wywołaniu w dolnej części PaintSurface procedury obsługi. Tuż przed tym kodem zobaczysz jednak wywołanie Scale metody SKCanvas. Ta Scale metoda skaluje się w poziomie o 1 (co nie robi nic), ale w pionie o –1, co skutecznie odwraca wszystko do góry nogami. Środek obrotu jest ustawiony na punkt (0, yText), gdzie yText jest pionowym środek kanwy, pierwotnie obliczany jako info.Height podzielony przez 2.

Pamiętaj, że Skia używa gradientu do kolorowania obiektów graficznych przed przekształceniami kanwy. Po narysowanym tekście nierefleksowanym prostokąt jest przesunięty tak textBounds , aby odpowiadał wyświetlanemu tekstowi:

textBounds.Offset(xText, yText);

Wywołanie CreateLinearGradient definiuje gradient od góry tego prostokąta do dołu. Gradient pochodzi z całkowicie przezroczystego niebieskiego (paint.Color.WithAlpha(0)) do 50% przezroczystego niebieskiego (paint.Color.WithAlpha(0x80)). Przekształcenie kanwy przerzuca tekst do góry nogami, więc 50% przezroczysty niebieski zaczyna się od punktu odniesienia i staje się przezroczysty w górnej części tekstu.