Lineární přechod SkiaSharp

Download Sample Stažení ukázky

Třída SKPaint definuje Color vlastnost, která se používá k tahům čar nebo výplní oblastí plnou barvou. Alternativně můžete čáry tahu nebo oblasti výplně přechody, které jsou postupné kombinace barev:

Linear Gradient Sample

Nejzásadnější typ přechodu je lineární přechod. Kombinace barev se vyskytuje na čáře (označované jako přechodová čára) z jednoho bodu do druhého. Čáry, které jsou kolmé na přechodovou čáru, mají stejnou barvu. Lineární přechod vytvoříte pomocí jedné ze dvou statických SKShader.CreateLinearGradient metod. Rozdíl mezi těmito dvěma přetíženími spočívá v tom, že jedna obsahuje maticovou transformaci a druhá ne.

Tyto metody vrátí objekt typu SKShader , který jste nastavili na Shader vlastnost SKPaint. Shader Pokud je vlastnost nenulová, přepíše Color vlastnost. Každá čára, která je tahem nebo libovolná oblast vyplněná tímto SKPaint objektem, je založena na přechodu, nikoli na plné barvě.

Poznámka:

Vlastnost Shader je ignorována při zahrnutí SKPaint objektu DrawBitmap do volání. Vlastnost můžete použít Color k nastavení úrovně průhlednosti pro zobrazení rastrového obrázku (jak je popsáno v článku Zobrazení rastrových obrázků SkiaSharp), ale nemůžete použít Shader vlastnost pro zobrazení rastrového obrázku s průhledností SKPaint přechodu. Další techniky jsou k dispozici pro zobrazení rastrových obrázků s přechodovými transparencimi: Jsou popsány v článcích SkiaSharp kruhové přechody a režimy kompozitování a kombinace SkiaSharp.

Přechody rohového do rohu

Lineární přechod se často rozšiřuje z jednoho rohu obdélníku na druhý. Pokud je počáteční bod levým horním rohem obdélníku, přechod může rozšířit:

  • svisle do levého dolního rohu
  • vodorovně do pravého horního rohu
  • diagonálně do pravého dolního rohu

Diagonální lineární přechod je znázorněn na první stránce v části SkiaSharp Shaders a Other Effects vzorku SkiaSharpFormsDemos. Stránka Přechod rohového rohu vytvoří v jeho konstruktoru SKCanvasView . Obslužná rutina PaintSurface vytvoří SKPaint objekt v using příkazu a pak definuje obdélník čtverce o rozměrech 300 pixelů uprostřed na plátně:

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

Vlastnost ShaderSKPaint je přiřazena návratová SKShader hodnota ze statické SKShader.CreateLinearGradient metody. Pět argumentů je následující:

  • Počáteční bod přechodu, který je tady nastavený na levý horní roh obdélníku
  • Koncový bod přechodu, který je tady nastavený na pravý dolní roh obdélníku
  • Pole dvou nebo více barev, které přispívají k přechodu
  • Matice float hodnot označující relativní pozici barev v rámci přechodové čáry
  • Člen výčtu SKShaderTileMode označující, jak se přechod chová nad konci přechodové čáry

Po vytvoření přechodového objektu DrawRect metoda nakreslí obdélník čtverce o rozměrech 300 pixelů pomocí objektu SKPaint , který obsahuje shader. Tady běží na iOSu, Androidu a Univerzální platforma Windows (UPW):

Corner-to-Corner Gradient

Přechodová čára je definována dvěma body zadanými jako první dva argumenty. Všimněte si, že tyto body jsou relativní vzhledem k plátnu a ne k grafickému objektu zobrazenému přechodem. Podél přechodové čáry barva postupně přechází z červené v levém horním rohu na modrou v pravém dolním rohu. Každá čára, která je kolmá na přechodovou čáru, má konstantní barvu.

Matice float hodnot zadaná jako čtvrtý argument má shodu 1:1 s polem barev. Hodnoty označují relativní pozici podél přechodové čáry, kde se tyto barvy vyskytují. 0 znamená, že Red se vyskytuje na začátku přechodové čáry a 1 znamená, že Blue se vyskytuje na konci čáry. Čísla musí být vzestupná a měla by být v rozsahu od 0 do 1. Pokud nejsou v této oblasti, upraví se tak, aby byly v této oblasti.

Dvě hodnoty v matici lze nastavit na jinou hodnotu než 0 a 1. Vyzkoušejte následující:

new float[] { 0.25f, 0.75f }

Teď je celá první čtvrtina přechodové čáry čistě červená a poslední čtvrtletí je čistě modrá. Kombinace červené a modré je omezena na centrální polovinu přechodové čáry.

Obecně platí, že tyto hodnoty pozice budete chtít umístit rovnoměrně od 0 do 1. Pokud tomu tak je, můžete jednoduše zadat null jako čtvrtý argument CreateLinearGradient.

I když je tento přechod definován mezi dvěma rohy obdélníku čtverce o rozměrech 300 pixelů, není omezen na vyplnění tohoto obdélníku. Přechodová stránka rohového rohu obsahuje další kód, který reaguje na klepnutí nebo kliknutí myší na stránku. Pole drawBackground se přepíná mezi true klepnutím a false po každém klepnutí. Pokud je truehodnota , PaintSurface obslužná rutina použije stejný SKPaint objekt k vyplnění celého plátna a pak nakreslí černý obdélník označující menší obdélník:

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

Po klepnutí na obrazovku uvidíte toto:

Corner-to-Corner Gradient Full

Všimněte si, že se přechod opakuje ve stejném vzoru nad body definující přechodovou čáru. K tomuto opakování dochází, protože poslední argument CreateLinearGradient je SKShaderTileMode.Repeat. (Za chvíli uvidíte další možnosti.)

Všimněte si také, že body, které použijete k určení přechodové čáry, nejsou jedinečné. Čáry, které jsou kolmé na přechodovou čáru, mají stejnou barvu, takže existuje nekonečný počet přechodových čar, které můžete určit pro stejný efekt. Například při vyplňování obdélníku vodorovným přechodem můžete určit levý horní a pravý horní roh nebo levý dolní a pravý dolní roh nebo jakékoli dva body, které jsou i s těmito čarami a rovnoběžně s těmito čarami.

Interaktivní experiment

S lineárními přechody můžete interaktivně experimentovat se stránkou Interaktivní lineární přechod . Tato stránka používá InteractivePage třídu představenou v článku Tři způsoby kreslení oblouku. InteractivePage Zpracovává TouchEffect události k udržování kolekce TouchPoint objektů, které můžete pohybovat prsty nebo myší.

Soubor XAML připojí TouchEffect k nadřazené položce objektu SKCanvasView a také obsahuje Picker možnost výběru jednoho ze tří členů výčtu SKShaderTileMode :

<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 v souboru kódu vytvoří dva TouchPoint objekty pro počáteční a koncové body lineárního přechodu. Obslužná PaintSurface rutina definuje pole tří barev (pro přechod z červené na zelenou až modrou) a získá aktuální SKShaderTileMode z 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);
        }
        ···
    }
}

Obslužná rutina PaintSurface vytvoří SKShader objekt ze všech informací a použije ho k obarvení celého plátna. float Pole hodnot je nastaveno na nullhodnotu . V opačném případě byste tento parametr nastavili na matici s hodnotami 0, 0,5 a 1.

Většina PaintSurface obslužné rutiny se věnuje zobrazení několika objektů: dotykové body jako kruhy obrysu, přechodová čára a čáry kolmé na přechodové čáry v dotykových bodech:

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

Přechodová čára spojující obě touchpointy je snadné kreslit, ale kolmé čáry vyžadují další práci. Přechodová čára je převedena na vektor, normalizována tak, aby měla délku jedné jednotky a pak otočila o 90 stupňů. Tento vektor je pak dán délkou 200 pixelů. Používá se k kreslení čtyř čar, které se rozšiřují od dotykových bodů, aby byly kolmé na přechodovou čáru.

Kolmé čáry odpovídají začátku a konci přechodu. Co se stane nad těmito řádky, závisí na nastavení výčtu SKShaderTileMode :

Interactive Linear Gradient

Tři snímky obrazovky ukazují výsledky tří různých hodnot SKShaderTileMode. Snímek obrazovky s iOSem, SKShaderTileMode.Clampkterý jenom rozšiřuje barvy na ohraničení přechodu. Možnost SKShaderTileMode.Repeat na snímku obrazovky s Androidem ukazuje, jak se vzor přechodu opakuje. Možnost SKShaderTileMode.Mirror na snímku obrazovky UPW také opakuje vzor, ale vzor je pokaždé obrácený, což vede k žádným přerušením barev.

Přechody na přechodech

Třída SKShader definuje žádné veřejné vlastnosti nebo metody s výjimkou Dispose. Objekty SKShader vytvořené jeho statickými metodami jsou proto neměnné. I když použijete stejný přechod pro dva různé objekty, pravděpodobně budete chtít přechod mírně lišit. K tomu budete muset vytvořit nový SKShader objekt.

Na stránce Text přechodu se zobrazí text a závorka, které jsou barevné s podobnými přechody:

Gradient Text

Jedinými rozdíly v přechodech jsou počáteční a koncové body. Přechod použitý k zobrazení textu je založený na dvou bodech v rozích ohraničujícího obdélníku textu. Pro pozadí jsou dva body založeny na celém plátně. Tady je kód:

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

Vlastnost Shader objektu SKPaint je nejprve nastavena tak, aby zobrazovala přechod pro pokrytí pozadí. Přechodové body jsou nastavené na levý horní a pravý dolní roh plátna.

Kód nastaví vlastnost objektu TextSizeSKPaint tak, aby text byl zobrazen na 90 % šířky plátna. Hranice textu se používají k výpočtu xText a yText hodnotám, DrawText které se mají předat metodě, aby se text zacentruje.

Přechodové body druhého CreateLinearGradient volání ale musí odkazovat na levý horní a pravý dolní roh textu vzhledem k plátnu, když se zobrazí. Toho dosáhnete tak textBounds , že obdélník posunete o stejné xText hodnoty a yText hodnoty:

textBounds.Offset(xText, yText);

Nyní je možné použít levý horní a pravý dolní roh obdélníku k nastavení počátečního a koncového bodu přechodu.

Animace přechodu

Přechod můžete animovat několika způsoby. Jedním zpřístupch Stránka Animace přechodu přesune dva body kolem kruhu, který je uprostřed na plátně. Poloměr tohoto kruhu je polovina šířky nebo výšky plátna, podle toho, co je menší. Počáteční a koncové body jsou na tomto kruhu proti sobě a přechod se posune z bílé na černou s režimem Mirror dlaždice:

Gradient Animation

Konstruktor vytvoří SKCanvasView. Logika OnAppearing animace a OnDisappearing metody zpracovávají:

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 vypočítá angle hodnotu, která je animované od 0 do 2π každých 3 sekund.

Tady je jeden způsob, jak vypočítat dva přechodové body. Hodnota SKPoint s názvem vector je vypočítána tak, aby se rozšířila ze středu plátna na bod v poloměru kruhu. Směr tohoto vektoru je založen na sinusových a kosinusových hodnotách úhlu. Pak se vypočítají dva opačné přechodové body: Jeden bod se vypočítá odečtením tohoto vektoru od středového bodu a druhý bod se vypočítá přidáním vektoru do středového bodu:

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

Poněkud jiný přístup vyžaduje méně kódu. Tento přístup používá metodu SKShader.CreateLinearGradient přetížení s maticovou transformací jako poslední argument. Tento přístup je verze v ukázce SkiaSharpFormsDemos :

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

Pokud je šířka plátna menší než výška, nastaví se dva přechodové body na (0, 0) a (info.Width, 0). Transformace otočení předaná jako poslední argument, který CreateLinearGradient efektivně otočí tyto dva body kolem středu obrazovky.

Všimněte si, že pokud je úhel 0, není k dispozici otočení a dva přechodové body jsou levé horní a pravé horní rohy plátna. Tyto body nejsou stejné přechodové body vypočítané, jak je znázorněno v předchozím CreateLinearGradient volání. Tyto body jsou ale rovnoběžné s vodorovnou přechodovou čárou, která rozseká střed plátna, a výsledkem je stejný přechod.

Duhový přechod

Stránka Duhový přechod nakreslí duhu z levého horního rohu plátna do pravého dolního rohu. Ale tento duhový přechod není jako skutečná duha. Je to rovné místo zakřivení, ale je založené na osmi barvách HSL (sytost-luminosity), které jsou určeny cyklicky přes hodnoty odstínu 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);
}

Tento kód je součástí obslužné rutiny PaintSurface uvedené níže. Obslužná rutina začíná vytvořením cesty, která definuje šestistranný mnohoúhelník, který se rozšiřuje z levého horního rohu plátna do pravého dolního rohu:

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

Dva přechodové body v CreateLinearGradient metodě jsou založeny na dvou bodech, které definují tuto cestu: Oba body jsou blízko levému hornímu rohu. První je na horním okraji plátna a druhá je na levém okraji plátna. Tady je výsledek:

Rainbow Gradient Faulty

Toto je zajímavý obrázek, ale není to úplně záměr. Problémem je, že při vytváření lineárního přechodu jsou čáry konstantní barvy kolmé na přechodovou čáru. Přechodová čára je založena na bodech, kde se obrázek dotkne horní a levé strany, a tato čára obvykle není kolmá k okrajům obrázku, které se rozšiřují do pravého dolního rohu. Tento přístup by fungoval pouze v případě, že plátno bylo čtvercové.

Chcete-li vytvořit správný přechod duhy, musí být přechodová čára kolmá k okraji duhy. To je zapletenější výpočet. Vektor musí být definován paralelně s dlouhou stranou obrázku. Vektor je otočený o 90 stupňů, aby byl kolmý na tuto stranu. Pak se prodloužila tak, aby byla šířka obrázku vynásobením rainbowWidth. Dva přechodové body se počítají na základě bodu na straně obrázku a tohoto bodu plus vektoru. Tady je kód, který se zobrazí na stránce Rainbow Gradient v ukázce SkiaSharpFormsDemos :

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

Teď jsou barvy duhy zarovnané s obrázkem:

Rainbow Gradient

Nekonečné barvy

Přechod duhy se používá také na stránce Nekonečná barva . Tato stránka nakreslí nekonečno znaménko pomocí objektu cesty popsaného v článku Tři typy Bézierovy křivky. Obrázek se pak vybarví animovaným přechodem duhy, který se průběžně přemístí přes obrázek.

Konstruktor vytvoří SKPath objekt popisující znaménko nekonečna. Po vytvoření cesty může konstruktor získat také obdélníkové hranice cesty. Pak vypočítá hodnotu s názvem gradientCycleLength. Pokud je přechod založený na levém horním a pravém dolním rohu pathBounds obdélníku, gradientCycleLength jedná se o celkovou vodorovnou šířku vzoru přechodu:

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 také vytvoří colors pole pro duhu a SKCanvasView objekt.

Přepsání OnAppearing a OnDisappearing metody provádějí režijní náklady na animaci. Metoda OnTimerTick animuje offset pole od 0 do gradientCycleLength každé dvě 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;
    }
    ···
}

Nakonec obslužná rutina PaintSurface vykreslí znaménko nekonečna. Vzhledem k tomu, že cesta obsahuje záporné a kladné souřadnice obklopované středovým bodem (0, 0), Translate slouží transformace na plátně k posunu na střed. Za transformací Scale následuje transformace, která použije faktor měřítka, který zvětší znaménko nekonečna co největší a přitom zůstane v rozsahu 95 % šířky a výšky plátna.

Všimněte si, že konstanta STROKE_WIDTH se přidá do šířky a výšky obdélníku ohraničujícího cestu. Cesta bude tahem s čárou této šířky, takže velikost vykreslené nekonečno je zvýšena o polovinu této šířky na všech čtyřech stranách:

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

Podívejte se na body předané jako první dva argumenty SKShader.CreateLinearGradient. Tyto body jsou založeny na původní cestě ohraničující obdélník. První bod je (–250, –100) a druhý je (250, 100). Interní pro SkiaSharp, tyto body jsou předmětem aktuální transformace plátna, aby byly správně zarovnané se zobrazeným znaménkem nekonečna.

Bez posledního argumentu CreateLinearGradientbyste viděli duhový přechod, který se rozšiřuje z levého horního znaménka nekonečna do pravého dolního rohu. (Přechod ve skutečnosti přesahuje z levého horního rohu do pravého dolního rohu ohraničujícího obdélníku. Vykreslené znaménko nekonečna je větší než ohraničující obdélník o polovinu STROKE_WIDTH hodnoty na všech stranách. Vzhledem k tomu, že přechod je na začátku i na konci červený a přechod se vytvoří pomocí SKShaderTileMode.Repeat, rozdíl není znatelný.)

S tímto posledním argumentem CreateLinearGradientse přechodový vzor průběžně přemístit přes obrázek:

Infinity Colors

Průhlednost a přechody

Barvy, které přispívají k přechodu, můžou zahrnovat průhlednost. Místo přechodu, který zeslabí z jedné barvy na jinou, může přechod zesvětlovat z barvy na průhlednou.

Tuto techniku můžete použít pro některé zajímavé efekty. Jeden z klasických příkladů ukazuje grafický objekt s jeho odrazem:

Reflection Gradient

Text, který je vzhůru nohama, je barevný s přechodem, který je 50% průhledný v horní části, aby byl úplně průhledný v dolní části. Tyto úrovně průhlednosti jsou spojeny s alfa hodnotami 0x80 a 0.

Obslužná rutina PaintSurface na stránce přechodu Reflexe škáluje velikost textu na 90 % šířky plátna. Potom vypočítá xText a yText hodnoty pro umístění textu, který má být vodorovně zarovnaný na střed, ale nachází se na účaří odpovídající svislém středu stránky:

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

Tyto xText hodnoty a yText hodnoty jsou stejné hodnoty, které slouží k zobrazení reflektujícího textu ve DrawText volání v dolní části obslužné rutiny PaintSurface . Těsně před tímto kódem však uvidíte volání Scale metody SKCanvas. Tato Scale metoda horizontálně škáluje o 1 (což nic nedělá), ale svisle o –1, což efektivně překlopí všechno vzhůru nohama. Střed otáčení je nastaven na bod (0), yTextkde yText je svislé střed plátna, původně vypočítaný jako info.Height dělený 2.

Nezapomeňte, že Skia používá přechod k barevným grafickým objektům před transformací plátna. Po vykreslení nerelektovaného textu se obdélník posune tak, textBounds aby odpovídal zobrazenému textu:

textBounds.Offset(xText, yText);

Volání CreateLinearGradient definuje přechod z horní části tohoto obdélníku do dolní části. Přechod je z zcela průhledné modré (paint.Color.WithAlpha(0)) na 50% průhlednou modrou (paint.Color.WithAlpha(0x80)). Transformace plátna překlopí text vzhůru nohama, takže 50% průhledná modrá začíná na účaří a stane se průhlednou v horní části textu.