Degradados circulares SkiaSharp

La clase SKShader define métodos estáticos para crear cuatro tipos diferentes de degradados. En el artículo Degradado lineal de SkiaSharp se analiza el método CreateLinearGradient. En este artículo se tratan los otros tres tipos de degradados, todos ellos basados en círculos.

El método CreateRadialGradient crea un degradado que emana del centro de un círculo:

Ejemplo de degradado radial

El método CreateSweepGradient crea un degradado que barre alrededor del centro de un círculo:

Ejemplo de degradado de barrido

El tercer tipo de degradado es bastante inusual. Se denomina degradado cónico de dos puntos y se define mediante el método CreateTwoPointConicalGradient. El degradado se extiende de un círculo a otro:

Ejemplo de degradado cónico

Si los dos círculos son de distinto tamaño, el gradiente adopta la forma de un cono.

En este artículo se exploran estos degradados con más detalle.

Degradado radial

El método CreateRadialGradient tiene la siguiente sintaxis:

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

Una sobrecarga CreateRadialGradient también incluye un parámetro de matriz de transformación.

Los dos primeros argumentos especifican el centro de un círculo y un radio. El degradado comienza en ese centro y se extiende hacia fuera durante radius píxeles. Lo que ocurre más allá de radius depende del argumento SKShaderTileMode. El parámetro colors es una matriz de dos o más colores (igual que en los métodos de degradado lineal) y colorPos es una matriz de enteros en el intervalo de 0 a 1. Estos enteros indican las posiciones relativas de los colores a lo largo de esa línea radius. Puede establecer ese argumento en null para espaciar los colores por igual.

Si utiliza CreateRadialGradient para rellenar un círculo, puede establecer el centro del degradado en el centro del círculo y el radio del degradado en el radio del círculo. En ese caso, el argumento SKShaderTileMode no tiene ningún efecto en la representación del degradado. Pero si el área rellenada por el degradado es mayor que el círculo definido por el degradado, el argumento SKShaderTileMode tiene un efecto profundo sobre lo que sucede fuera del círculo.

El efecto de SKShaderMode se muestra en la páginaDegradado radial de la muestra. El archivo XAML de esta página crea una instancia de Picker que le permite seleccionar uno de los tres miembros de la enumeración 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>

El archivo de código subyacente colorea todo el lienzo con un degradado radial. El centro del degradado se establece en el centro del lienzo y el radio se establece en 100 píxeles. El degradado consta de solo dos colores, negro y blanco:

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

Este código crea un degradado con negro en el centro, desvaneciéndose gradualmente a blanco a 100 píxeles del centro. Lo que sucede más allá de ese radio depende del argumento SKShaderTileMode:

Degradado radial

En los tres casos, el degradado rellena el lienzo. En la pantalla de iOS de la izquierda, el degradado más allá del radio continúa con el último color, que es blanco. Es el resultado de SKShaderTileMode.Clamp. La pantalla Android muestra el efecto de SKShaderTileMode.Repeat: a 100 píxeles desde el centro, el degradado comienza de nuevo con el primer color, que es negro. El degradado se repite cada 100 píxeles de radio.

La pantalla Plataforma universal de Windows de la derecha muestra cómo SKShaderTileMode.Mirror hace que los degradados se alternen. El primer degradado es de negro en el centro a blanco en un radio de 100 píxeles. El siguiente es blanco desde el radio de 100 píxeles a negro en un radio de 200 píxeles, y el siguiente degradado se invierte de nuevo.

Puede usar más de dos colores en un degradado radial. En el ejemplo de degradado arco iris se crea una matriz de ocho colores que corresponden a los colores del arco iris y terminan en rojo, y también una matriz de ocho valores de posición:

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

Supongamos que el mínimo del ancho y alto del lienzo es 1000, lo que significa que el valor rainbowWidth es 250. Los valores outerRadius y innerRadius se establecen en 1000 y 750, respectivamente. Estos valores se usan para calcular la matriz positions; los ocho valores oscilan entre 0,75f y 1. El valor radius se utiliza para trazar el círculo. El valor de 875 significa que el ancho de trazo de 250 píxeles se extiende entre el radio de 750 píxeles y el radio de 1000 píxeles:

Arco iris degradado

Si rellenaras todo el lienzo con este degradado, verías que es rojo dentro del radio interno. Esto se debe a que la matriz positions no comienza con 0. El primer color se utiliza para los desplazamientos de 0 al primer valor de la matriz. El degradado también es rojo más allá del radio exterior. Es el resultado del modo de mosaico Clamp. Como el degradado se utiliza para trazar una línea gruesa, estas zonas rojas no son visibles.

Degradados radiales para enmascaramiento

Al igual que los degradados lineales, los degradados radiales pueden incorporar colores transparentes o parcialmente transparentes. Esta característica es útil para un proceso denominado enmascaramiento, que oculta parte de una imagen para acentuar otra parte de la imagen.

En la página de máscara de degradado radial se muestra un ejemplo. El programa carga uno de los mapas de bits de recursos. Los campos CENTER y RADIUS se determinaron a partir de un examen del mapa de bits y hacen referencia a un área que se debe resaltar. El controlador PaintSurface comienza calculando un rectángulo para mostrar el mapa de bits y, a continuación, lo muestra en ese rectá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);
        }
    }
}

Después de dibujar el mapa de bits, un código sencillo convierte CENTER y RADIUS en center y radius, que se refieren al área resaltada en el mapa de bits que se ha escalado y desplazado para su visualización. Estos valores se usan para crear un degradado radial con ese centro y radio. Los dos colores comienzan en transparente en el centro y para el primer 60 % del radio. A continuación, el degradado se vuelve blanco:

Máscara de degradado radial

Este enfoque no es la mejor manera de enmascarar un mapa de bits. El problema es que la máscara tiene sobre todo un color blanco, que se eligió para que coincidiera con el fondo del lienzo. Si el fondo es de otro color —o tal vez un degradado— no coincidirá. Un enfoque mejor para el enmascaramiento se muestra en el artículo Modos de fusión Porter-Duff de SkiaSharp.

Degradados radiales para resaltados especulares

Cuando una luz incide sobre una superficie redondeada, esta refleja la luz en muchas direcciones, pero parte de la luz rebota directamente en el ojo del espectador. Esto con frecuencia crea la apariencia de un área blanca borrosa en la superficie llamada resaltado especular.

En gráficos tridimensionales, los resaltados especulares suelen ser el resultado de los algoritmos utilizados para determinar las trayectorias de la luz y el sombreado. En gráficos bidimensionales, a veces se agregan resaltados especulares para sugerir la apariencia de una superficie 3D. Un resaltado especular puede transformar un círculo rojo plano en una bola roja redonda.

La página Resaltado especular radial usa un degradado radial para hacer exactamente eso. El controlador PaintSurface comienza calculando un radio para el círculo, y dos valores SKPoint: un center y un offCenter que está a medio camino entre el centro y el borde superior izquierdo del 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);
        }
    }
}

La llamada CreateRadialGradient crea un degradado que comienza en ese punto offCenter con blanco y termina con rojo a una distancia de la mitad del radio. Este es su aspecto:

Resaltado especular radial

Si observa detenidamente este degradado, puede decidir que está defectuoso. El degradado se centra en un punto concreto, y quizá le gustaría que fuera un poco menos simétrico para reflejar la superficie redondeada. En ese caso, puede preferir el resaltado especular que se muestra a continuación en la sección Degradados cónicos para los resaltados especulares.

Degradado de barrido

El método CreateSweepGradient tiene la sintaxis más sencilla de todos los métodos de creación de degradado:

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

Es solo un centro, una matriz de colores y las posiciones de los colores. El degradado comienza a la derecha del punto central y barre 360 grados en el sentido de las agujas del reloj alrededor del centro. Observe que no hay ningún parámetro SKShaderTileMode.

También hay disponible una sobrecarga CreateSweepGradient con un parámetro de transformación de matriz. Puede aplicar una transformación de rotación al degradado para cambiar el punto inicial. También puede aplicar una transformación de escala para cambiar la dirección de las agujas del reloj a la contraria.

La página Degradado de barrido usa un degradado de barrido para colorear un círculo con un ancho de trazo de 50 píxeles:

Degradado de barrido

La clase SweepGradientPage define una matriz de ocho colores con valores de matiz diferentes. Observe que la matriz comienza y termina con rojo (un valor de matiz de 0 o 360), que aparece en el extremo derecho en las capturas de pantalla:

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

El programa también implementa un TapGestureRecognizer que habilita algún código al final del controlador PaintSurface. Este código usa el mismo degradado para rellenar el lienzo:

Degradado de barrido completo

Estas capturas de pantalla muestran que el degradado rellena cualquier área que haya coloreado. Si el degradado no comienza y termina con el mismo color, habrá una discontinuidad a la derecha del punto central.

Degradado cónico de dos puntos

El método CreateTwoPointConicalGradient tiene la siguiente sintaxis:

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

Los parámetros comienzan con puntos centrales y radios para dos círculos, denominados círculo inicial y círculo final. Los tres parámetros restantes son los mismos que para CreateLinearGradient y CreateRadialGradient. Una sobrecarga CreateTwoPointConicalGradient incluye una transformación de matriz.

El degradado comienza en el círculo inicial y termina en el círculo final. El parámetro SKShaderTileMode rige lo que sucede más allá de los dos círculos. El degradado cónico de dos puntos es el único degradado que no rellena completamente un área. Si los dos círculos tienen el mismo radio, el degradado está restringido a un rectángulo con un ancho que es el mismo que el diámetro de los círculos. Si los dos círculos tienen radios diferentes, el degradado forma un cono.

Es probable que quiera experimentar con el degradado cónico de dos puntos, por lo que la página Degradado cónico se deriva de InteractivePage para permitir el desplazamiento de dos puntos táctiles para los dos radios del círculo:

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

El archivo de código subyacente define los dos objetos TouchPoint con radio fijo de 50 y 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);
            }
        }
    }
}

La matriz colors es roja, verde y azul. El código hacia la parte inferior del controlador PaintSurface dibuja los dos puntos táctiles como círculos negros para que no obstruyan el degradado.

Observe que la llamada DrawRect usa el degradado para colorear todo el lienzo. En el caso general, sin embargo, gran parte del lienzo permanece sin colorear por el degradado. Este es el programa que muestra tres configuraciones posibles:

Degradado cónico

La pantalla de iOS de la izquierda muestra el efecto del ajuste SKShaderTileMode de Clamp. El degradado comienza con rojo en el interior del borde del círculo más pequeño que está enfrente del lado más cercano al segundo círculo. El valor Clamp también hace que el rojo continúe hasta el punto del cono. El degradado termina con azul en el borde exterior del círculo más grande que está más cerca del primer círculo, pero continúa con azul dentro de ese círculo y más allá.

La pantalla de Android es similar, pero con un SKShaderTileMode de Repeat. Ahora es más claro que el degradado comienza dentro del primer círculo y termina fuera del segundo círculo. El ajuste Repeat hace que el degradado se repita de nuevo con rojo dentro del círculo más grande.

La pantalla de UWP muestra lo que sucede cuando el círculo más pequeño se mueve completamente dentro del círculo más grande. El degradado deja de ser un cono y pasa a llenar toda el área. El efecto es similar al degradado radial, pero es asimétrico si el círculo más pequeño no se centra exactamente dentro del círculo más grande.

Puede dudar de la utilidad práctica del degradado cuando un círculo está anidado en otro, pero es ideal para un resaltado especular.

Degradados cónicos para resaltados especulares

Anteriormente en este artículo vimos cómo usar un degradado radial para crear un resaltado especular. También puede utilizar el degradado cónico de dos puntos para este fin, y puede que prefiera cómo queda:

Resaltado especular cónico

La apariencia asimétrica sugiere mejor la superficie redondeada del objeto.

El código de dibujo de la página Resaltado especular cónico es el mismo que la página Resaltado especular radial, excepto para el 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);
        }
    }
}

Los dos círculos tienen centros de offCenter y center. El círculo centrado en center está asociado a un radio que abarca toda la bola, pero el círculo centrado en offCenter tiene un radio de solo un píxel. El degradado comienza efectivamente en ese punto y termina en el borde de la bola.