Линейный градиент SkiaSharp
Класс SKPaint
определяет Color
свойство, которое используется для штрихов или заливки областей сплошным цветом. Кроме того, линии штрихов или области заливки с градиентами, которые являются постепенными смешения цветов:
Самый простой тип градиента — линейный градиент. Сочетание цветов происходит на линии (называемой градиентной линией) от одной точки к другой. Линии, которые являются перпендикулярными к линии градиента, имеют тот же цвет. Вы создаете линейный градиент с помощью одного из двух статических SKShader.CreateLinearGradient
методов. Разница между двумя перегрузками заключается в том, что один включает преобразование матрицы, а другой — нет.
Эти методы возвращают объект типа SKShader
, заданного свойством Shader
SKPaint
. Shader
Если свойство не равно null, оно переопределяет Color
свойство. Любая линия, заполненная или заполненная этой SKPaint
областью, основана на градиенте, а не на сплошном цвете.
Примечание.
Свойство Shader
игнорируется при включении SKPaint
объекта в DrawBitmap
вызов. Свойство можно использовать Color
SKPaint
для задания уровня прозрачности для отображения растрового изображения (как описано в статье Displaying SkiaSharp bitmaps), но нельзя использовать 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
возвращаемого значения из статического SKShader.CreateLinearGradient
метода. Пять аргументов приведены следующим образом:
- Начальная точка градиента, установленная здесь в левом верхнем углу прямоугольника.
- Конечная точка градиента, установленная здесь в правом нижнем углу прямоугольника.
- Массив двух или более цветов, которые способствуют градиенту
- Массив значений, указывающий
float
относительное положение цветов в линии градиента - Элемент
SKShaderTileMode
перечисления, указывающий, как градиент работает за пределами конца линии градиента.
После создания DrawRect
объекта градиента метод рисует прямоугольник 300 пикселей с помощью SKPaint
объекта, включающего шейдер. Здесь он работает в 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
переключается между true
false
и с каждым касанием. Если значение равно 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
. Снимок экрана SKShaderTileMode.Clamp
iOS, который просто расширяет цвета на границе градиента. На 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
. OnDisappearing
Методы OnAppearing
обрабатывают логику анимации:
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
значение, анимированное от 0 до 2π каждые 3 секунды.
Вот один из способов вычисления двух градиентных точек. Именованное 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 (hue-saturation-luminosity) цвета, которые определяются велоспортом через оттенки значений от 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);
}
}
}
}
Теперь цвета радуги выровнены с рисунком:
Цвета бесконечности
Радужный градиент также используется на странице "Цвета бесконечности ". Эта страница рисует знак бесконечности с помощью объекта пути, описанного в статье "Три типа кривых Bézier". Затем изображение цветом с анимированным градиентом радуги, который постоянно разметается по изображению.
Конструктор создает 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
анимирует offset
поле от 0 до gradientCycleLength
каждых двух секунд:
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), 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.
Обработчик PaintSurface
на странице Рефлексия ion Gradient масштабирует размер текста до 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
значения являются теми же значениями, которые используются для отображения отраженного текста в вызове в DrawText
нижней части обработчика PaintSurface
. Однако перед этим кодом вы увидите вызов Scale
метода SKCanvas
. Этот 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% прозрачный синий начинается на базовом уровне и становится прозрачным в верхней части текста.