SkiaSharp 線性漸層
類別 SKPaint
會 Color
定義屬性,用來以純色繪製線條或填滿區域。 或者,您可以用漸層填滿線條或填滿區域 ,這些漸層會逐漸混合色彩:
最基本的漸層類型是 線性 漸層。 色彩的混合發生在從一個點到另一個點的線條上(稱為 漸層線)。 與漸層線條垂直的線條具有相同色彩。 您可以使用兩個靜態 SKShader.CreateLinearGradient
方法的其中一個來建立線性漸層。 兩個多載之間的差異在於一個包含矩陣轉換,另一個則不包含。
這些方法會傳回您設定為 Shader
屬性之 型SKShader
別SKPaint
的物件。 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);
}
}
}
}
以下是點選畫面之後會看到的內容:
請注意,漸層會在定義漸層線條的點以外,以相同的模式重複。 之所以重複,是因為 的最後一個自變數 CreateLinearGradient
是 SKShaderTileMode.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);
}
···
}
}
處理程式 PaintSurface
會 SKShader
從所有資訊建立 物件,並用它來著色整個畫布。 值的陣列 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
不會定義任何公用屬性或方法 Dispose
。 SKShader
因此,靜態方法所建立的物件是不可變的。 即使您對兩個不同的物件使用相同的漸層,還是可能會稍微改變漸層。 若要這樣做,您必須建立新的 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% 顯示。 文字界限是用來計算 xText
和 yText
值,以傳遞至 DrawText
方法以置中文字。
不過,第二次 CreateLinearGradient
呼叫的漸層點必須在顯示時參考相對於畫布的文字左上角和右下角。 這可藉由將矩形移 textBounds
出相同的 xText
和 yText
值來完成:
textBounds.Offset(xText, yText);
現在矩形的左上角和右下角可用來設定漸層的起點和終點。
以動畫顯示漸層
有數種方式可以產生漸層的動畫效果。 其中一種方法是建立起點和終點的動畫效果。 [ 漸層動畫 ] 頁面會在以畫布為中心的圓形中移動兩個點。 這個圓圈的半徑是畫布的寬度或高度的一半,無論哪一個較小。 這個圓形的起點和終點彼此相反,漸層會以 Mirror
磚模式從白色到黑色:
建構函式會 SKCanvasView
建立 。 OnAppearing
和 OnDisappearing
方法會處理動畫邏輯:
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;
}
···
}
方法 OnTimerTick
會 angle
計算每 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
時,漸層模式會持續橫掃影像:
透明度和漸層
參與漸層的色彩可以納入透明度。 漸層可以淡化為透明,而不是從某個色彩淡出到另一個色彩的漸層。
您可以將這項技術用於一些有趣的效果。 其中一個傳統範例顯示具有其反映的圖形物件:
倒置的文字會以漸層著色,其上層為 50% 透明,在底部完全透明。 這些透明度層級與 0x80和 0 的 Alpha 值相關聯。
PaintSurface
反思 漸層頁面中的處理程式會將文字的大小調整為畫布寬度的 90%。 然後,它會計算 xText
和 yText
值,將文字置中水準,但位於對應到頁面垂直中心的基準上:
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);
}
}
}
這些xText
和yText
值是用來在處理程式底部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% 的透明藍色從基準開始,並在文字頂端變成透明。