Lineární přechod SkiaSharp
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:
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 Shader
SKPaint
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):
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 true
hodnota , 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:
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 null
hodnotu . 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
:
Tři snímky obrazovky ukazují výsledky tří různých hodnot SKShaderTileMode
. Snímek obrazovky s iOSem, SKShaderTileMode.Clamp
který 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:
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 TextSize
SKPaint
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:
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:
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:
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 CreateLinearGradient
byste 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 CreateLinearGradient
se přechodový vzor průběžně přemístit přes obrázek:
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:
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), yText
kde 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.