Partilhar via


Os gradientes circulares SkiaSharp

A SKShader classe define métodos estáticos para criar quatro tipos diferentes de gradientes. O artigo SkiaSharp gradiente linear discute o CreateLinearGradient método. Este artigo aborda os outros três tipos de gradientes, todos baseados em círculos.

O CreateRadialGradient método cria um gradiente que emana do centro de um círculo:

Amostra de gradiente radial

O CreateSweepGradient método cria um gradiente que varre o centro de um círculo:

Amostra de gradiente de varredura

O terceiro tipo de gradiente é bastante incomum. É chamado de gradiente cônico de dois pontos e é definido pelo CreateTwoPointConicalGradient método. O gradiente se estende de um círculo a outro:

Amostra de gradiente cônico

Se os dois círculos são tamanhos diferentes, então o gradiente assume a forma de um cone.

Este artigo explora esses gradientes com mais detalhes.

O gradiente radial

O CreateRadialGradient método tem a seguinte sintaxe:

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

Uma CreateRadialGradient sobrecarga também inclui um parâmetro de matriz de transformação.

Os dois primeiros argumentos especificam o centro de um círculo e um raio. O gradiente começa nesse centro e se estende para fora para radius pixels. O que acontece além radius depende do SKShaderTileMode argumento. O colors parâmetro é uma matriz de duas ou mais cores (assim como nos métodos de gradiente linear) e colorPos é uma matriz de inteiros no intervalo de 0 a 1. Esses inteiros indicam as posições relativas das cores ao longo dessa radius linha. Você pode definir esse argumento para null espaçar igualmente as cores.

Se você usar CreateRadialGradient para preencher um círculo, poderá definir o centro do gradiente para o centro do círculo e o raio do gradiente para o raio do círculo. Nesse caso, o SKShaderTileMode argumento não tem efeito sobre a renderização do gradiente. Mas se a área preenchida pelo gradiente é maior do que o círculo definido pelo gradiente, então o SKShaderTileMode argumento tem um efeito profundo sobre o que acontece fora do círculo.

O efeito de SKShaderMode é demonstrado na página Gradiente Radial na amostra. O arquivo XAML para esta página instancia um que permite selecionar um Picker dos três membros da SKShaderTileMode enumeração:

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

O arquivo code-behind colore toda a tela com um gradiente radial. O centro do gradiente é definido como o centro da tela e o raio é definido como 100 pixels. O degradê é composto por apenas duas cores, preto e branco:

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

Esse código cria um gradiente com preto no centro, desaparecendo gradualmente para branco a 100 pixels do centro. O que acontece além desse raio depende do SKShaderTileMode argumento:

Gradiente Radial

Nos três casos, o gradiente preenche a tela. Na tela do iOS à esquerda, o gradiente além do raio continua com a última cor, que é o branco. Esse é o resultado de SKShaderTileMode.Clamp. A tela do Android mostra o efeito de SKShaderTileMode.Repeat: A 100 pixels do centro, o gradiente começa novamente com a primeira cor, que é preto. O gradiente se repete a cada 100 pixels de raio.

A tela da Plataforma Universal do Windows à direita mostra como faz com que SKShaderTileMode.Mirror os gradientes alternem direções. O primeiro gradiente é de preto no centro para branco em um raio de 100 pixels. O próximo é branco do raio de 100 pixels para preto em um raio de 200 pixels, e o próximo gradiente é invertido novamente.

Você pode usar mais de duas cores em um gradiente radial. O exemplo Rainbow Arc Gradient cria uma matriz de oito cores correspondentes às cores do arco-íris e terminando com vermelho, e também uma matriz de oito valores de posição:

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

Suponha que o mínimo da largura e altura da tela seja 1000, o que significa que o rainbowWidth valor é 250. Os outerRadius valores e innerRadius são definidos como 1000 e 750, respectivamente. Esses valores são usados para calcular a positions matriz, os oito valores variam de 0,75f a 1. O radius valor é usado para acariciar o círculo. O valor de 875 significa que a largura do traçado de 250 pixels se estende entre o raio de 750 pixels e o raio de 1000 pixels:

Gradiente de arco arco-íris

Se você preenchesse toda a tela com esse gradiente, veria que ela está vermelha dentro do raio interno. Isso ocorre porque a positions matriz não começa com 0. A primeira cor é usada para deslocamentos de 0 até o primeiro valor de matriz. O gradiente também é vermelho além do raio externo. Esse é o resultado do Clamp modo de bloco. Como o gradiente é usado para acariciar uma linha grossa, essas áreas vermelhas não são visíveis.

Gradientes radiais para mascaramento

Como gradientes lineares, gradientes radiais podem incorporar cores transparentes ou parcialmente transparentes. Esse recurso é útil para um processo chamado mascaramento, que oculta parte de uma imagem para acentuar outra parte da imagem.

A página Máscara de gradiente radial mostra um exemplo. O programa carrega um dos bitmaps de recurso. Os CENTER campos e RADIUS foram determinados a partir de um exame do bitmap e fazem referência a uma área que deve ser destacada. O PaintSurface manipulador começa calculando um retângulo para exibir o bitmap e, em seguida, o exibe nesse retângulo:

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

Depois de desenhar o bitmap, alguns códigos simples convertem CENTER e RADIUS para center e radius, que se referem à área realçada no bitmap que foi dimensionada e deslocada para exibição. Esses valores são usados para criar um gradiente radial com esse centro e raio. As duas cores começam em transparente no centro e nos primeiros 60% do raio. O gradiente então desaparece para branco:

Máscara de gradiente radial

Essa abordagem não é a melhor maneira de mascarar um bitmap. O problema é que a máscara tem principalmente uma cor de branco, que foi escolhida para combinar com o fundo da tela. Se o plano de fundo for de alguma outra cor — ou talvez um gradiente em si — ele não corresponderá. Uma abordagem melhor para o mascaramento é mostrada no artigo SkiaSharp Porter-Duff blend modes.

Gradientes radiais para realces especulares

Quando uma luz atinge uma superfície arredondada, ela reflete a luz em muitas direções, mas parte da luz salta diretamente no olho do espectador. Isso geralmente cria a aparência de uma área branca difusa na superfície chamada de realce especular.

Em gráficos tridimensionais, os destaques especulares geralmente resultam dos algoritmos usados para determinar caminhos de luz e sombreamento. Em gráficos bidimensionais, destaques especulares às vezes são adicionados para sugerir a aparência de uma superfície 3D. Um realce especular pode transformar um círculo vermelho plano em uma bola vermelha redonda.

A página Realce Especular Radial usa um gradiente radial para fazer exatamente isso. O PaintSurface manipulador calcula um raio para o círculo e dois SKPoint valores — a center e um offCenter que está a meio caminho entre o centro e a borda superior esquerda do círculo:

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

A CreateRadialGradient chamada cria um gradiente que começa nesse offCenter ponto com branco e termina com vermelho a uma distância de metade do raio. Esta é a aparência dele:

Destaque Especular Radial

Se você olhar atentamente para esse gradiente, poderá decidir que ele é falho. O gradiente é centrado em torno de um ponto específico, e você poderia desejar que fosse um pouco menos simétrico para refletir a superfície arredondada. Nesse caso, você pode preferir o realce especular mostrado abaixo na seção Gradientes cônicos para destaques especulares.

O gradiente de varredura

O CreateSweepGradient método tem a sintaxe mais simples de todos os métodos de criação de gradiente:

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

É apenas um centro, uma variedade de cores e as posições de cores. O gradiente começa à direita do ponto central e varre 360 graus no sentido horário ao redor do centro. Observe que não há parâmetro SKShaderTileMode .

Uma CreateSweepGradient sobrecarga com um parâmetro de transformação de matriz também está disponível. Você pode aplicar uma transformação de rotação ao gradiente para alterar o ponto inicial. Você também pode aplicar uma transformação de escala para alterar a direção do sentido horário para o sentido anti-horário.

A página Gradiente de varredura usa um gradiente de varredura para colorir um círculo com uma largura de traçado de 50 pixels:

Gradiente de varredura

A SweepGradientPage classe define uma matriz de oito cores com valores de matiz diferentes. Observe que a matriz começa e termina com vermelho (um valor de matiz de 0 ou 360), que aparece na extrema direita nas capturas de tela:

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

O programa também implementa um TapGestureRecognizer que habilita algum código no final do PaintSurface manipulador. Esse código usa o mesmo gradiente para preencher a tela:

Varrer gradiente completo

Essas capturas de tela demonstram que o gradiente preenche qualquer área colorida por ele. Se o gradiente não começar e terminar com a mesma cor, haverá uma descontinuidade à direita do ponto central.

O gradiente cônico de dois pontos

O CreateTwoPointConicalGradient método tem a seguinte sintaxe:

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

Os parâmetros começam com pontos centrais e raios para dois círculos, referidos como círculo inicial e círculo final . Os três parâmetros restantes são os mesmos que para CreateLinearGradient e CreateRadialGradient. Uma CreateTwoPointConicalGradient sobrecarga inclui uma transformação de matriz.

O gradiente começa no círculo inicial e termina no círculo final. O SKShaderTileMode parâmetro rege o que acontece além dos dois círculos. O gradiente cônico de dois pontos é o único gradiente que não preenche totalmente uma área. Se os dois círculos tiverem o mesmo raio, o gradiente será restrito a um retângulo com uma largura igual ao diâmetro dos círculos. Se os dois círculos têm raios diferentes, o gradiente forma um cone.

É provável que você queira experimentar o gradiente cônico de dois pontos, então a página Gradiente cônico deriva de para permitir que dois pontos de toque sejam movidos InteractivePage para os dois raios circulares:

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

O arquivo code-behind define os dois TouchPoint objetos com raios fixos de 50 e 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);
            }
        }
    }
}

A colors matriz é vermelha, verde e azul. O código na parte inferior do PaintSurface manipulador desenha os dois pontos de toque como círculos pretos para que eles não obstruam o gradiente.

Observe que DrawRect a chamada usa o gradiente para colorir toda a tela. No caso geral, no entanto, grande parte da tela permanece sem cor pelo gradiente. Aqui está o programa mostrando três configurações possíveis:

Gradiente Cônico

A tela do iOS à esquerda mostra o SKShaderTileMode efeito da configuração do Clamp. O gradiente começa com vermelho dentro da borda do círculo menor que é oposto ao lado mais próximo do segundo círculo. O Clamp valor também faz com que o vermelho continue até o ponto do cone. O gradiente termina com azul na borda externa do círculo maior que está mais próximo do primeiro círculo, mas continua com azul dentro desse círculo e além.

A tela do Android é semelhante, mas com um SKShaderTileMode de Repeat. Agora está mais claro que o gradiente começa dentro do primeiro círculo e termina fora do segundo círculo. A Repeat configuração faz com que o gradiente se repita novamente com vermelho dentro do círculo maior.

A tela UWP mostra o que acontece quando o círculo menor é movido inteiramente dentro do círculo maior. O gradiente deixa de ser um cone e passa a preencher toda a área. O efeito é semelhante ao gradiente radial, mas é assimétrico se o círculo menor não estiver exatamente centralizado dentro do círculo maior.

Você pode duvidar da utilidade prática do gradiente quando um círculo está aninhado em outro, mas é ideal para um destaque especular.

Gradientes cônicos para realces especulares

No início deste artigo, você viu como usar um gradiente radial para criar um realce especular. Você também pode usar o gradiente cônico de dois pontos para essa finalidade e talvez prefira sua aparência:

Destaque especular cônico

A aparência assimétrica sugere melhor a superfície arredondada do objeto.

O código de desenho na página Realce Especular Cônico é o mesmo que a página Realce Especular Radial , exceto para o sombreador:

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

Os dois círculos têm centros de offCenter e center. O círculo centrado em center está associado a um raio que engloba toda a bola, mas o círculo centrado em offCenter tem um raio de apenas um pixel. O gradiente efetivamente começa nesse ponto e termina na borda da bola.