Бөлісу құралы:


Циклические градиенты SkiaSharp

Класс SKShader определяет статические методы для создания четырех различных типов градиентов. В статье о линейном градиенте SkiaSharp рассматривается CreateLinearGradient метод. В этой статье рассматриваются три типа градиентов, все из которых основаны на кругах.

Метод CreateRadialGradient создает градиент, исходящий из центра круга:

Пример радиального градиента

Метод CreateSweepGradient создает градиент, который перемещается по центру круга:

Пример градиента очистки

Третий тип градиента довольно необычный. Он называется двухточечным коническим градиентом и определяется методом CreateTwoPointConicalGradient . Градиент расширяется от одного круга к другому:

Пример конического градиента

Если два круга имеют разные размеры, то градиент принимает форму конуса.

В этой статье подробно рассматриваются эти градиенты.

Радиальный градиент

Метод CreateRadialGradient имеет следующий синтаксис:

public static SKShader CreateRadialGradient (SKPoint center,
                                             Single radius,
                                             SKColor[] colors,
                                             Single[] colorPos,
                                             SKShaderTileMode mode)

Перегрузка CreateRadialGradient также включает параметр матрицы преобразования.

Первые два аргумента указывают центр круга и радиуса. Градиент начинается с этого центра и расширяется наружу для radius пикселей. Что происходит вне radius зависимости от аргумента SKShaderTileMode . Параметр colors представляет собой массив двух или более цветов (как и в линейных градиентных методах) и colorPos представляет собой массив целых чисел в диапазоне от 0 до 1. Эти целые числа указывают относительные позиции цветов вдоль этой radius линии. Этот аргумент можно задать для null равного пространства цветов.

Если вы используете CreateRadialGradient для заполнения круга, можно задать центр градиента в центре круга и радиус градиента до радиуса круга. В этом случае SKShaderTileMode аргумент не влияет на отрисовку градиента. Но если область, заполненная градиентом, больше круга, определенного градиентом, то SKShaderTileMode аргумент имеет глубокое влияние на то, что происходит за пределами круга.

SKShaderMode Эффект демонстрируется на странице радиального градиента в примере. XAML-файл для этой страницы создает экземпляр, Picker который позволяет выбрать один из трех элементов SKShaderTileMode перечисления:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:skia="clr-namespace:SkiaSharp;assembly=SkiaSharp"
             xmlns:skiaforms="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             x:Class="SkiaSharpFormsDemos.Effects.RadialGradientPage"
             Title="Radial Gradient">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <skiaforms:SKCanvasView x:Name="canvasView"
                                Grid.Row="0"
                                PaintSurface="OnCanvasViewPaintSurface" />

        <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>
</ContentPage>

Код за файлом цветом всего холста с радиальным градиентом. Центр градиента установлен в центре холста, а радиус — 100 пикселей. Градиент состоит всего из двух цветов, черных и белых:

public partial class RadialGradientPage : ContentPage
{
    public RadialGradientPage ()
    {
        InitializeComponent ();
    }

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

        SKShaderTileMode tileMode =
            (SKShaderTileMode)(tileModePicker.SelectedIndex == -1 ?
                                        0 : tileModePicker.SelectedItem);

        using (SKPaint paint = new SKPaint())
        {
            paint.Shader = SKShader.CreateRadialGradient(
                                new SKPoint(info.Rect.MidX, info.Rect.MidY),
                                100,
                                new SKColor[] { SKColors.Black, SKColors.White },
                                null,
                                tileMode);

            canvas.DrawRect(info.Rect, paint);
        }
    }
}

Этот код создает градиент с черным цветом в центре, постепенно исчезая до белого 100 пикселей от центра. Что происходит за пределами этого радиуса, зависит от аргумента SKShaderTileMode :

Радиальный градиент

Во всех трех случаях градиент заполняет холст. На экране iOS слева градиент за радиусом продолжается с последним цветом, который является белым. Это результат SKShaderTileMode.Clamp. Экран Android показывает эффект SKShaderTileMode.Repeat: В 100 пикселей от центра градиент начинается снова с первого цвета, который является черным. Градиент повторяется каждые 100 пикселей радиуса.

На экране универсальная платформа Windows справа показано, как SKShaderTileMode.Mirror градиенты вызывают альтернативные направления. Первый градиент от черного в центре до белого в радиусе от 100 пикселей. Следующий — белый от радиуса 100 пикселей к черному в радиусе 200 пикселей, а следующий градиент снова перевернут.

Можно использовать более двух цветов в радиальном градиенте. Пример "Радуга Дуга Градиент " создает массив из восьми цветов, соответствующих цветам радуги и заканчивая красным, а также массивом из восьми значений позиции:

public class RainbowArcGradientPage : ContentPage
{
    public RainbowArcGradientPage ()
    {
        Title = "Rainbow Arc 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())
        {
            float rainbowWidth = Math.Min(info.Width, info.Height) / 4f;

            // Center of arc and gradient is lower-right corner
            SKPoint center = new SKPoint(info.Width, info.Height);

            // Find outer, inner, and middle radius
            float outerRadius = Math.Min(info.Width, info.Height);
            float innerRadius = outerRadius - rainbowWidth;
            float radius = outerRadius - rainbowWidth / 2;

            // Calculate the colors and positions
            SKColor[] colors = new SKColor[8];
            float[] positions = new float[8];

            for (int i = 0; i < colors.Length; i++)
            {
                colors[i] = SKColor.FromHsl(i * 360f / 7, 100, 50);
                positions[i] = (i + (7f - i) * innerRadius / outerRadius) / 7f;
            }

            // Create sweep gradient based on center and outer radius
            paint.Shader = SKShader.CreateRadialGradient(center,
                                                         outerRadius,
                                                         colors,
                                                         positions,
                                                         SKShaderTileMode.Clamp);
            // Draw a circle with a wide line
            paint.Style = SKPaintStyle.Stroke;
            paint.StrokeWidth = rainbowWidth;

            canvas.DrawCircle(center, radius, paint);
        }
    }
}

Предположим, что минимальное значение ширины и высоты холста равно 1000, что означает, что rainbowWidth значение равно 250. Для outerRadius и innerRadius значений задано значение 1000 и 750 соответственно. Эти значения используются для вычисления массива; восемь значений positions варьируются от 0,75f до 1. Значение radius используется для поглаживания круга. Значение 875 означает, что ширина штриха 250 пикселей расширяется между радиусом 750 пикселей и радиусом 1000 пикселей:

Радуга Дуга Градиент

Если вы заполняли весь холст этим градиентом, вы увидите, что он красный в пределах внутреннего радиуса. Это связано с тем, что positions массив не начинается с 0. Первый цвет используется для смещения от 0 до первого значения массива. Градиент также красный за пределами внешнего радиуса. Это результат режима плитки Clamp . Поскольку градиент используется для поглаживания толстой линии, эти красные области не видны.

Радиальные градиенты для маскирования

Как и линейные градиенты, радиальные градиенты могут включать прозрачные или частично прозрачные цвета. Эта функция полезна для процесса, называемого маскированием, который скрывает часть изображения, чтобы подчеркнуть другую часть изображения.

На странице "Радиальный градиент маски " показан пример. Программа загружает одно из растровых карт ресурсов. RADIUS Поля CENTER были определены из проверки растрового изображения и ссылки на область, которая должна быть выделена. Обработчик PaintSurface начинается с вычисления прямоугольника для отображения растрового изображения, а затем отображает его в этом прямоугольнике:

public class RadialGradientMaskPage : ContentPage
{
    SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(
        typeof(RadialGradientMaskPage),
        "SkiaSharpFormsDemos.Media.MountainClimbers.jpg");

    static readonly SKPoint CENTER = new SKPoint(180, 300);
    static readonly float RADIUS = 120;

    public RadialGradientMaskPage ()
    {
        Title = "Radial Gradient Mask";

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

        // Find rectangle to display bitmap
        float scale = Math.Min((float)info.Width / bitmap.Width,
                                (float)info.Height / bitmap.Height);

        SKRect rect = SKRect.Create(scale * bitmap.Width, scale * bitmap.Height);

        float x = (info.Width - rect.Width) / 2;
        float y = (info.Height - rect.Height) / 2;
        rect.Offset(x, y);

        // Display bitmap in rectangle
        canvas.DrawBitmap(bitmap, rect);

        // Adjust center and radius for scaled and offset bitmap
        SKPoint center = new SKPoint(scale * CENTER.X + x,
                                     scale * CENTER.Y + y);
        float radius = scale * RADIUS;

        using (SKPaint paint = new SKPaint())
        {
            paint.Shader = SKShader.CreateRadialGradient(
                                center,
                                radius,
                                new SKColor[] { SKColors.Transparent,
                                                SKColors.White },
                                new float[] { 0.6f, 1 },
                                SKShaderTileMode.Clamp);

            // Display rectangle using that gradient
            canvas.DrawRect(rect, paint);
        }
    }
}

После рисования растрового изображения некоторые простые коды преобразуются CENTER и RADIUS center radiusв, а также ссылаются на выделенную область в растровом изображении, которая была масштабирована и смещена для отображения. Эти значения используются для создания радиального градиента с этим центром и радиусом. Два цвета начинаются с прозрачного в центре и для первых 60 % радиуса. Затем градиент исчезает до белого:

Радиальная маска градиента

Этот подход не является лучшим способом маскировать растровое изображение. Проблема заключается в том, что маска в основном имеет цвет белого цвета, который был выбран для сопоставления фона холста. Если фон является другим цветом , или, возможно, градиент сам по себе , он не будет соответствовать. Лучший подход к маскировке показан в статьях SkiaSharp Porter-Duff blend режимов.

Радиальные градиенты для спектральных выделений

Когда свет ударяет округленной поверхностью, он отражает свет во многих направлениях, но некоторые из света отскочили непосредственно в глаза зрителя. Это часто создает внешний вид нечеткой белой области на поверхности, называемой зрительным выделением.

В трехмерной графике зриемые выделения часто результаты из алгоритмов, используемых для определения легких путей и заливки. В двухмерной графике иногда добавляются зриемые выделения, чтобы предложить внешний вид трехмерной поверхности. Спекулярное выделение может преобразовать плоский красный круг в круглый красный шар.

На странице "Радиальный градиент" используется радиальный градиент, чтобы точно это сделать. Обработчик PaintSurface выполняется путем вычисления радиуса для круга и двух SKPoint значений — a center и то offCenter , что находится на полпути между центром и верхним левым краем круга:

public class RadialSpecularHighlightPage : ContentPage
{
    public RadialSpecularHighlightPage()
    {
        Title = "Radial Specular Highlight";

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

        float radius = 0.4f * Math.Min(info.Width, info.Height);
        SKPoint center = new SKPoint(info.Rect.MidX, info.Rect.MidY);
        SKPoint offCenter = center - new SKPoint(radius / 2, radius / 2);

        using (SKPaint paint = new SKPaint())
        {
            paint.Shader = SKShader.CreateRadialGradient(
                                offCenter,
                                radius / 2,
                                new SKColor[] { SKColors.White, SKColors.Red },
                                null,
                                SKShaderTileMode.Clamp);

            canvas.DrawCircle(center, radius, paint);
        }
    }
}

Вызов CreateRadialGradient создает градиент, начинающийся с offCenter белого и заканчивающийся красным на расстоянии половины радиуса. Он выглядит следующим образом.

Радиальное зрительное выделение

Если вы внимательно посмотрите на этот градиент, вы можете решить, что он является недостатком. Градиент находится в центре определенной точки, и вы могли бы пожелать, чтобы это было немного менее симметрично, чтобы отразить округленную поверхность. В этом случае вы можете предпочесть зрительное выделение, показанное ниже в разделе Конические градиенты для градиентов для спектрических выделений.

Градиент очистки

Метод CreateSweepGradient имеет самый простой синтаксис всех методов создания градиентов:

public static SKShader CreateSweepGradient (SKPoint center,
                                            SKColor[] colors,
                                            Single[] colorPos)

Это просто центр, массив цветов и позиции цвета. Градиент начинается справа от центра и отметает 360 градусов по часовой стрелке по центру. Обратите внимание, что нет SKShaderTileMode параметра.

Также CreateSweepGradient доступна перегрузка с параметром преобразования матрицы. Преобразование поворота можно применить к градиенту, чтобы изменить начальную точку. Можно также применить преобразование масштабирования, чтобы изменить направление с часовой стрелки на по часовой стрелке.

Страница "Градиент", использующая градиент очистки, использует градиент для цвета круга с шириной штриха 50 пикселей:

Градиент очистки

Класс SweepGradientPage определяет массив из восьми цветов с различными значениями оттенков. Обратите внимание, что массив начинается и заканчивается красным цветом (значение 0 или 360), которое отображается в правом углу экрана:

public class SweepGradientPage : ContentPage
{
    bool drawBackground;

    public SweepGradientPage ()
    {
        Title = "Sweep Gradient";

        SKCanvasView canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;

        TapGestureRecognizer tap = new TapGestureRecognizer();
        tap.Tapped += (sender, args) =>
        {
            drawBackground ^= true;
            canvasView.InvalidateSurface();
        };
        canvasView.GestureRecognizers.Add(tap);
    }

    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())
        {
            // Define an array of rainbow colors
            SKColor[] colors = new SKColor[8];

            for (int i = 0; i < colors.Length; i++)
            {
                colors[i] = SKColor.FromHsl(i * 360f / 7, 100, 50);
            }

            SKPoint center = new SKPoint(info.Rect.MidX, info.Rect.MidY);

            // Create sweep gradient based on center of canvas
            paint.Shader = SKShader.CreateSweepGradient(center, colors, null);

            // Draw a circle with a wide line
            const int strokeWidth = 50;
            paint.Style = SKPaintStyle.Stroke;
            paint.StrokeWidth = strokeWidth;

            float radius = (Math.Min(info.Width, info.Height) - strokeWidth) / 2;
            canvas.DrawCircle(center, radius, paint);

            if (drawBackground)
            {
                // Draw the gradient on the whole canvas
                paint.Style = SKPaintStyle.Fill;
                canvas.DrawRect(info.Rect, paint);
            }
        }
    }
}

Программа также реализует код TapGestureRecognizer , который включает некоторый код в конце обработчика PaintSurface . Этот код использует тот же градиент для заполнения холста:

Градиент Градиент полный

На этих снимках экрана показано, что градиент заполняет любую область цветом. Если градиент не начинается и заканчивается с тем же цветом, будет разрыв справа от центра точки.

Двухточечный конический градиент

Метод CreateTwoPointConicalGradient имеет следующий синтаксис:

public static SKShader CreateTwoPointConicalGradient (SKPoint startCenter,
                                                      Single startRadius,
                                                      SKPoint endCenter,
                                                      Single endRadius,
                                                      SKColor[] colors,
                                                      Single[] colorPos,
                                                      SKShaderTileMode mode)

Параметры начинаются с центральных точек и радии для двух кругов, называемых начальным кругом и конечным кругом. Остальные три параметра совпадают с CreateLinearGradient остальными параметрами и CreateRadialGradient. Перегрузка CreateTwoPointConicalGradient включает преобразование матрицы.

Градиент начинается в начальном круге и заканчивается в конце круга. Параметр SKShaderTileMode управляет тем, что происходит за пределами двух кругов. Двухточечный конический градиент является единственным градиентом, который не полностью заполняет область. Если два круга имеют одинаковый радиус, градиент ограничен прямоугольником с шириной, которая совпадает с диаметром кругов. Если два круга имеют разные радии, градиент формирует конус.

Скорее всего, вы захотите поэкспериментировать с двумя точками конического градиента, поэтому коническая градиентная страница наследуется от InteractivePage того, чтобы разрешить перемещению двух точек касания для двух кругов радии:

<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.ConicalGradientPage"
                       Title="Conical 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 объекта с фиксированным радии 50 и 100:

public partial class ConicalGradientPage : InteractivePage
{
    const int RADIUS1 = 50;
    const int RADIUS2 = 100;

    public ConicalGradientPage ()
    {
        touchPoints = new TouchPoint[2];

        touchPoints[0] = new TouchPoint
        {
            Center = new SKPoint(100, 100),
            Radius = RADIUS1
        };

        touchPoints[1] = new TouchPoint
        {
            Center = new SKPoint(300, 300),
            Radius = RADIUS2
        };

        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.CreateTwoPointConicalGradient(touchPoints[0].Center,
                                                                  RADIUS1,
                                                                  touchPoints[1].Center,
                                                                  RADIUS2,
                                                                  colors,
                                                                  null,
                                                                  tileMode);
            canvas.DrawRect(info.Rect, paint);
        }

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

Массив colors красный, зеленый и синий. Код в нижней части PaintSurface обработчика рисует две точки касания как черные круги, чтобы они не препятствовали градиенту.

Обратите внимание, что DrawRect вызов использует градиент для цвета всего холста. Однако в общем случае большая часть холста остается некрасимой градиентом. Ниже приведена программа с тремя возможными конфигурациями:

Конический градиент

На экране iOS слева показан эффект SKShaderTileMode параметра Clamp. Градиент начинается с красного внутри края меньшего круга, который находится напротив стороны, ближайшей к второму кругу. Значение Clamp также приводит к тому, что красный цвет продолжается до точки конуса. Градиент заканчивается синим по внешнему краю большего круга, который ближе всего к первому кругу, но продолжается синим в пределах этого круга и за ее пределами.

Экран Android аналогичен, но с .SKShaderTileMode Repeat Теперь ясно, что градиент начинается внутри первого круга и заканчивается за пределами второго круга. Этот Repeat параметр приводит к повторному повтору градиента с красным цветом внутри большего круга.

На экране UWP показано, что происходит, когда меньший круг перемещается полностью внутри большего круга. Градиент перестает быть конусом и вместо этого заполняет всю область. Эффект аналогичен радиальному градиенту, но это асимметрично, если меньший круг не находится точно в центре большого круга.

Вы можете сомневаться в практической полезности градиента, когда один круг вложен в другой, но он идеально подходит для зрительного выделения.

Конические градиенты для спектрических выделений

Ранее в этой статье вы узнали, как использовать радиальный градиент для создания зрительного выделения. Вы также можете использовать двухточечный конический градиент для этой цели, и вы можете предпочесть, как он выглядит:

Коническое спекулярное выделение

Асимметричный внешний вид лучше предлагает округленную поверхность объекта.

Код рисования на странице "Коническое выделение" совпадает со страницей "Радиальное выделение" за исключением шейдера:

public class ConicalSpecularHighlightPage : ContentPage
{
    ···
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        ···
        using (SKPaint paint = new SKPaint())
        {
            paint.Shader = SKShader.CreateTwoPointConicalGradient(
                                offCenter,
                                1,
                                center,
                                radius,
                                new SKColor[] { SKColors.White, SKColors.Red },
                                null,
                                SKShaderTileMode.Clamp);

            canvas.DrawCircle(center, radius, paint);
        }
    }
}

Два круга имеют центры offCenter и center. Круг в центре связан с радиусом, охватывающим весь шар, но круг в центре center offCenter имеет радиус всего одного пикселя. Градиент фактически начинается с этого момента и заканчивается на краю мяча.