共用方式為


SkiaSharp 線性漸層

類別 SKPaintColor 定義屬性,用來以純色繪製線條或填滿區域。 或者,您可以用漸層填滿線條或填滿區域 ,這些漸層會逐漸混合色彩:

線性漸層範例

最基本的漸層類型是 線性 漸層。 色彩的混合發生在從一個點到另一個點的線條上(稱為 漸層線)。 與漸層線條垂直的線條具有相同色彩。 您可以使用兩個靜態 SKShader.CreateLinearGradient 方法的其中一個來建立線性漸層。 兩個多載之間的差異在於一個包含矩陣轉換,另一個則不包含。

這些方法會傳回您設定為 Shader 屬性之 型SKShaderSKPaint的物件。 Shader如果屬性為非 Null,則會覆寫 Color 屬性。 使用這個物件填滿的任何線條,或是使用這個 SKPaint 物件填滿的任何區域,都是以漸層而非純色為基礎。

注意

Shader當您SKPaint在呼叫中包含 DrawBitmap 物件時,會忽略 屬性。 您可以使用 的 Color 屬性SKPaint來設定顯示位圖的透明度層級(如顯示SkiaSharp位陣圖一文所述),但是您無法使用 Shader 屬性來顯示具有漸層透明度的點陣圖。 其他技術可用來顯示具有漸層透明度的位圖:這些技術會在SkiaSharp圓形漸層SkiaSharp組合和混合模式一文中說明。

角對角漸層

線性漸層通常會從矩形的一個角落延伸到另一個角落。 如果起點是矩形的左上角,漸層可以延伸:

  • 垂直到左下角
  • 水準到右上角
  • 對角線到右下角

對角線線性漸層會在範例的 SkiaSharp 著色器和其他效果區段的第一頁示範。 [ 邊角到角落漸 層] 頁面會 SKCanvasView 在其建構函式中建立 。 處理程式 PaintSurface 會在 SKPaint 語句中 using 建立 物件,然後定義以畫布為中心的 300 像素方形矩形:

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

Shader 屬性SKPaint會從靜態SKShader.CreateLinearGradient方法指派SKShader傳回值。 五個自變數如下所示:

  • 漸層的起點,在這裡設定為矩形的左上角
  • 漸層的終點,在這裡設定為矩形右下角
  • 造成漸層的兩種以上色彩陣列
  • 值陣列 float ,指出漸層線條內色彩的相對位置
  • 列舉的成員, SKShaderTileMode 指出漸層在漸層線結尾以外的行為

建立漸層對象之後, DrawRect 方法會使用 SKPaint 包含著色器的物件繪製 300 像素的方形矩形。 在這裡,它正在iOS、Android和 通用 Windows 平台上執行 (UWP):

角對角漸層

漸層線是由指定為前兩個自變數的兩個點所定義。 請注意,這些點與畫布相對而不是與漸層一起顯示的圖形物件。 沿著漸層線,色彩會逐漸從左上方的紅色轉換為右下角的藍色。 與漸層線條垂直的任何線條都有常數色彩。

指定為第四個自變數的值數位 float 具有與色彩陣列的一對一對應。 值表示這些色彩發生之漸層線條的相對位置。 在這裡,0 表示 Red 發生在漸層線的開頭,而 1 表示 Blue 發生在線條結尾。 數字必須遞增,且範圍應為 0 到 1。 如果它們不在該範圍內,則會調整為在該範圍內。

陣列中的兩個值可以設定為 0 和 1 以外的值。 試試看:

new float[] { 0.25f, 0.75f }

現在漸層線的整個第一季都是純紅色,而最後一個季度則是純藍色。 紅色和藍色的混合僅限於漸層線的中央半部分。

一般而言,您會想要將這些位置值平均從 0 空格到 1。 如果是這種情況,您可以直接提供 null 做為 的第四個自變數。CreateLinearGradient

雖然這個漸層定義在 300 像素方形矩形的兩個角落之間,但不限於填滿該矩形。 [ 邊角到角落漸層 ] 頁面包含一些額外的程序代碼,可響應頁面上的點選或滑鼠點選。 欄位drawBackground會在 和 false 之間true切換,每次點選。 如果值為 true,則 PaintSurface 處理程式會使用相同的 SKPaint 物件來填滿整個畫布,然後繪製黑色矩形,指出較小的矩形:

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

以下是點選畫面之後會看到的內容:

角對角漸層完整

請注意,漸層會在定義漸層線條的點以外,以相同的模式重複。 之所以重複,是因為 的最後一個自變數 CreateLinearGradientSKShaderTileMode.Repeat。 (您很快就會看到其他選項。

另請注意,您用來指定漸層線的點並不是唯一的。 垂直於漸層線的線條有相同的色彩,因此您可以針對相同的效果指定無限數量的漸層線。 例如,使用水準漸層填滿矩形時,您可以指定左上角和右上角、左下角和右下角,或任何兩個點,甚至與這些線條平行。

以互動方式實驗

您可以使用互動式線性漸層 頁面,以互動方式實驗線性漸層 。 此頁面使用InteractivePage第三篇文章中引進的類別來繪製弧線InteractivePage處理TouchEffect事件以維護您可以使用手指或滑鼠移動的物件TouchPoint集合。

XAML 檔案會將 TouchEffect 附加至 的 SKCanvasView 父系,也包含 Picker 可讓您選取列舉的三個成員之 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>

程序代碼後置檔案中的建構函式會為線性漸層的起點和終點建立兩個 TouchPoint 物件。 處理程式 PaintSurface 會定義三種色彩的數位(針對從紅色到綠色到藍色的漸層),並從 中取得目前的 SKShaderTileMode 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);
        }
        ···
    }
}

處理程式 PaintSurfaceSKShader 從所有資訊建立 物件,並用它來著色整個畫布。 值的陣列 float 設定為 null。 否則,若要將三個色彩相等空格,您會將該參數設定為值為 0、0.5 和 1 的陣列。

處理程式的大部分 PaintSurface 都專門用來顯示數個物件:觸控點做為外框圓形、漸層線,以及與觸控點上漸層線垂直的線條:

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

連接兩個觸控點的漸層線很容易繪製,但垂直線需要更多的工作。 漸層線會轉換成向量,正規化為一個單位的長度,然後旋轉 90 度。 然後,該向量會指定 200 像素的長度。 它用來繪製四條從觸控點延伸的線條,以垂直於漸層線。

垂直線與漸層的開頭和結尾相吻合。 超出這些行會發生什麼情況取決於列舉的 SKShaderTileMode 設定:

互動式線性漸層

三個螢幕快照顯示的三個不同值 SKShaderTileMode的結果。 iOS 螢幕快照顯示 SKShaderTileMode.Clamp,其只會延伸漸層框線上的色彩。 SKShaderTileMode.Repeat Android 螢幕快照中的選項會顯示如何重複漸層模式。 SKShaderTileMode.Mirror UWP 螢幕快照中的選項也會重複模式,但每次都會反轉該模式,因此不會有任何色彩不連續。

漸層上的漸層

除了 之外,類別 SKShader 不會定義任何公用屬性或方法 DisposeSKShader因此,靜態方法所建立的物件是不可變的。 即使您對兩個不同的物件使用相同的漸層,還是可能會稍微改變漸層。 若要這樣做,您必須建立新的 SKShader 物件。

[ 漸層文字 ] 頁面會顯示文字和具有類似漸層色彩的壓裂背景:

漸層文字

漸層中唯一的差異是起點和終點。 用來顯示文字的漸層是以文字周框邊角的兩個點為基礎。 針對背景,這兩個點是以整個畫布為基礎。 程式碼如下:

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

對象的 Shader 屬性 SKPaint 會先設定為顯示漸層以覆蓋背景。 漸層點會設定為畫布的左上角和右下角。

程式代碼會 TextSize 設定 物件的 屬性 SKPaint ,讓文字以畫布寬度的 90% 顯示。 文字界限是用來計算 xTextyText 值,以傳遞至 DrawText 方法以置中文字。

不過,第二次 CreateLinearGradient 呼叫的漸層點必須在顯示時參考相對於畫布的文字左上角和右下角。 這可藉由將矩形移 textBounds 出相同的 xTextyText 值來完成:

textBounds.Offset(xText, yText);

現在矩形的左上角和右下角可用來設定漸層的起點和終點。

以動畫顯示漸層

有數種方式可以產生漸層的動畫效果。 其中一種方法是建立起點和終點的動畫效果。 [ 漸層動畫 ] 頁面會在以畫布為中心的圓形中移動兩個點。 這個圓圈的半徑是畫布的寬度或高度的一半,無論哪一個較小。 這個圓形的起點和終點彼此相反,漸層會以 Mirror 磚模式從白色到黑色:

漸層動畫

建構函式會 SKCanvasView建立 。 OnAppearingOnDisappearing 方法會處理動畫邏輯:

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

方法 OnTimerTickangle 計算每 3 秒從 0 到 2π 產生動畫的值。

以下是計算兩個漸層點的其中一種方式。 SKPoint名為 vector 的值會計算為從畫布中央延伸至圓形半徑上的點。 這個向量的方向是以角度的正弦值和餘弦值為基礎。 接著會計算兩個相反的漸層點:一個點是藉由從中心點減去該向量來計算,而其他點則是藉由將向量加入中心點來計算:

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

稍微不同的方法需要較少的程序代碼。 此方法會使用 SKShader.CreateLinearGradient 多載方法搭配矩陣轉換做為最後一個自變數。 此方法是範例中的版本:

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

如果畫布的寬度小於高度,則兩個漸層點會設定為 (0, 0) 和 (info.Width, 0)。 旋轉轉換傳遞為最後一個自變數,以 CreateLinearGradient 有效地旋轉螢幕中央的這兩個點。

請注意,如果角度為0,則沒有旋轉,而兩個漸層點則是畫布的左上角和右上角。 這些點與上一 CreateLinearGradient 個呼叫中顯示的漸層點不同。 但是,這些點 與將畫布中央相比較的水準漸層線平行 ,而且會產生相同的漸層。

彩虹漸層

[彩虹漸層] 頁面會將畫布左上角的彩虹繪製到右下角。 但這種彩虹漸層不像真正的彩虹。 它是直的,而不是彎曲的,但它是以八種 HSL(色調飽和度-亮度)色彩為基礎,由迴圈從 0 到 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);
}

該程式代碼是如下所示處理程式的 PaintSurface 一部分。 處理程式會從建立路徑開始,該路徑會定義從畫布左上角延伸至右下角的六面多邊形:

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

方法中的 CreateLinearGradient 兩個漸層點是以定義此路徑的兩個點為基礎:這兩個點都接近左上角。 第一個是在畫布的上邊緣,第二個是在畫布的左邊緣。 結果如下︰

彩虹漸層故障

這是一個有趣的圖像,但它不是相當的意圖。 問題是,建立線性漸層時,常數色彩的線條會與漸層線垂直。 漸層線是根據圖形觸及上側和左側的點,而該線條通常與延伸至右下角的圖形邊緣不垂直。 只有在畫布是正方形時,此方法才能運作。

若要建立適當的彩虹漸層,漸層線必須與彩虹邊緣垂直。 這是一個更相關的計算。 必須定義與圖形長端平行的向量。 向量旋轉 90 度,使其與該側垂直。 然後,乘以 rainbowWidth將 乘以,將它加長為圖形的寬度。 這兩個漸層點是根據圖邊的點,以及該點加上向量來計算。 以下是範例中 [彩虹漸層 ] 頁面中出現的程序代碼:

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

現在彩虹色彩會與圖形對齊:

彩虹漸層

無限色彩

在 [無限色彩] 頁面中也會使用彩虹漸層。 此頁面使用三種類型的貝塞爾曲線一文中所述的路徑對象繪製無限號。 然後,影像會以動畫彩虹漸層著色,該漸層會持續橫掃影像。

建構函式會 SKPath 建立描述無限符號的物件。 建立路徑之後,建構函式也可以取得路徑的矩形界限。 然後,它會計算名為 gradientCycleLength的值。 如果漸層是以矩形左上角和右下角 pathBounds 為基礎,這個 gradientCycleLength 值就是漸層圖樣的總水平寬度:

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

建構函式也會建立 colors 彩虹和 SKCanvasView 對象的陣列。

OnDisappearing 方法的OnAppearing覆寫會執行動畫的額外負荷。 方法OnTimerTick會將欄位從 0 到gradientCycleLength每兩秒動畫offset一次:

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 轉譯無限號。 因為路徑包含圍繞中心點 (0, 0, 0) 的負和正座標, Translate 所以畫布上的轉換是用來將它移到中心。 轉譯轉換接著 Scale 套用縮放比例的轉換,讓無限號盡可能大,同時仍保持在畫布寬度和高度的 95% 以內。

請注意, STROKE_WIDTH 常數會新增至路徑周框的寬度和高度。 路徑會以此寬度的一行來繪製,因此轉譯的無限大大小大小會增加一半,而所有四側的寬度都會增加一半:

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

查看傳遞為的前兩個自變數 SKShader.CreateLinearGradient的點。 這些點是以原始路徑周框為基礎。 第一個點是 (–250, –100), 第二個是 (250, 100)。 在 SkiaSharp 內部,這些點會受限於目前的畫布轉換,使其與顯示的無限符號正確對齊。

如果沒有 的最後一個自變數, CreateLinearGradient您會看到從無限號左上角延伸至右下角的彩虹漸層。 (實際上,漸層會從左上角延伸至周框右下角。轉譯的無限大正負號大於周框的一半 STROKE_WIDTH 值。由於漸層在開頭和結尾都是紅色,且使用 建立 SKShaderTileMode.Repeat漸層,因此差異並不明顯。

將最後一個自變數設為 CreateLinearGradient時,漸層模式會持續橫掃影像:

無限色彩

透明度和漸層

參與漸層的色彩可以納入透明度。 漸層可以淡化為透明,而不是從某個色彩淡出到另一個色彩的漸層。

您可以將這項技術用於一些有趣的效果。 其中一個傳統範例顯示具有其反映的圖形物件:

反思 ion 漸層

倒置的文字會以漸層著色,其上層為 50% 透明,在底部完全透明。 這些透明度層級與 0x80和 0 的 Alpha 值相關聯。

PaintSurface反思 漸層頁面中的處理程式會將文字的大小調整為畫布寬度的 90%。 然後,它會計算 xTextyText 值,將文字置中水準,但位於對應到頁面垂直中心的基準上:

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

這些xTextyText值是用來在處理程式底部PaintSurface呼叫中DrawText顯示反映文字的相同值。 不過,在該程式代碼之前,您會看到 對 方法的SKCanvas呼叫Scale。 此方法 Scale 會水平調整為 1(不執行任何動作),但垂直調整為 –1,這實際上會反轉所有專案。 旋轉中心設定為點 (0, yText),其中 yText 是畫布的垂直中心,最初計算為 info.Height 除以 2。

請記住,Skia 會在畫布轉換之前使用漸層來著色圖形化物件。 繪製未切換的文字之後,矩形會移動, textBounds 使其對應至顯示的文字:

textBounds.Offset(xText, yText);

呼叫 CreateLinearGradient 會定義從該矩形頂端到底部的漸層。 漸層是從完全透明的藍色 (paint.Color.WithAlpha(0)) 到 50% 透明藍色 (paint.Color.WithAlpha(0x80))。 畫布轉換會反轉文字,讓 50% 的透明藍色從基準開始,並在文字頂端變成透明。