Tipos de relleno del trazado

Download SampleDescargar el ejemplo

Descubra los diferentes efectos posibles con los tipos de relleno de trazado SkiaSharp

Dos contornos en un trazado se pueden superponer y las líneas que componen un solo contorno pueden superponerse. Cualquier área cerrada se puede rellenar, pero es posible que no quiera rellenar todas las áreas cerradas. Este es un ejemplo:

Five-pointed star partially filles

Tiene un poco de control sobre esto. El algoritmo de llenado se rige por la propiedad SKFillType de SKPath, que se establece en un miembro de la enumeración SKPathFillType:

  • Winding, la opción predeterminada
  • EvenOdd
  • InverseWinding
  • InverseEvenOdd

Tanto el algoritmo de devanado como el de par-impar determinan si un área cerrada está llena o no, basándose en una línea hipotética trazada desde esa área hasta el infinito. Esa línea cruza una o varias líneas de límite que componen el trazado. Con el modo de devanado, si el número de líneas de límite dibujadas en una dirección equilibra el número de líneas dibujadas en la otra dirección, el área no está rellena. De lo contrario, el área está rellena. El algoritmo par-impar rellena un área si el número de líneas de límite es impar.

Con muchas rutas de acceso rutinarias, el algoritmo de devanado a menudo rellena todas las áreas cerradas de un trazado. El algoritmo par-impar generalmente genera resultados más interesantes.

El ejemplo clásico es una estrella de cinco puntas, como se muestra en la página Estrella de cinco puntas. El archivo FivePointedStarPage.xaml crea una instancia de dos vistas Picker para seleccionar el tipo de relleno del trazado y si el trazado está trazado, rellenado o ambos, y en qué orden:

<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.Paths.FivePointedStarPage"
             Title="Five-Pointed Star">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <Picker x:Name="fillTypePicker"
                Title="Path Fill Type"
                Grid.Row="0"
                Grid.Column="0"
                Margin="10"
                SelectedIndexChanged="OnPickerSelectedIndexChanged">
            <Picker.ItemsSource>
                <x:Array Type="{x:Type skia:SKPathFillType}">
                    <x:Static Member="skia:SKPathFillType.Winding" />
                    <x:Static Member="skia:SKPathFillType.EvenOdd" />
                    <x:Static Member="skia:SKPathFillType.InverseWinding" />
                    <x:Static Member="skia:SKPathFillType.InverseEvenOdd" />
                </x:Array>
            </Picker.ItemsSource>
            <Picker.SelectedIndex>
                0
            </Picker.SelectedIndex>
        </Picker>

        <Picker x:Name="drawingModePicker"
                Title="Drawing Mode"
                Grid.Row="0"
                Grid.Column="1"
                Margin="10"
                SelectedIndexChanged="OnPickerSelectedIndexChanged">
            <Picker.ItemsSource>
                <x:Array Type="{x:Type x:String}">
                    <x:String>Fill only</x:String>
                    <x:String>Stroke only</x:String>
                    <x:String>Stroke then Fill</x:String>
                    <x:String>Fill then Stroke</x:String>
                </x:Array>
            </Picker.ItemsSource>
            <Picker.SelectedIndex>
                0
            </Picker.SelectedIndex>
        </Picker>

        <skiaforms:SKCanvasView x:Name="canvasView"
                                Grid.Row="1"
                                Grid.Column="0"
                                Grid.ColumnSpan="2"
                                PaintSurface="OnCanvasViewPaintSurface" />
    </Grid>
</ContentPage>

El archivo de código subyacente usa ambos valores Picker para dibujar una estrella de cinco puntas:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    SKPoint center = new SKPoint(info.Width / 2, info.Height / 2);
    float radius = 0.45f * Math.Min(info.Width, info.Height);

    SKPath path = new SKPath
    {
        FillType = (SKPathFillType)fillTypePicker.SelectedItem
    };
    path.MoveTo(info.Width / 2, info.Height / 2 - radius);

    for (int i = 1; i < 5; i++)
    {
        // angle from vertical
        double angle = i * 4 * Math.PI / 5;
        path.LineTo(center + new SKPoint(radius * (float)Math.Sin(angle),
                                        -radius * (float)Math.Cos(angle)));
    }
    path.Close();

    SKPaint strokePaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Red,
        StrokeWidth = 50,
        StrokeJoin = SKStrokeJoin.Round
    };

    SKPaint fillPaint = new SKPaint
    {
        Style = SKPaintStyle.Fill,
        Color = SKColors.Blue
    };

    switch ((string)drawingModePicker.SelectedItem)
    {
        case "Fill only":
            canvas.DrawPath(path, fillPaint);
            break;

        case "Stroke only":
            canvas.DrawPath(path, strokePaint);
            break;

        case "Stroke then Fill":
            canvas.DrawPath(path, strokePaint);
            canvas.DrawPath(path, fillPaint);
            break;

        case "Fill then Stroke":
            canvas.DrawPath(path, fillPaint);
            canvas.DrawPath(path, strokePaint);
            break;
    }
}

Normalmente, el tipo de relleno de trazado solo debe afectar a rellenos y no trazos, pero los dos modos de Inverse afectan tanto a rellenos como a trazos. Para rellenos, los dos tipos Inverse rellenan áreas de forma opuesta para que el área fuera de la estrella se llene. En el caso de los trazos, los dos tipos Inverse colorearán todo excepto el trazo. El uso de estos tipos de relleno inverso puede producir algunos efectos extraños, como se muestra en la captura de pantalla de iOS:

Triple screenshot of the Five-Pointed Star page

En la captura de pantalla de Android se muestran los efectos pares e impares típicos, pero el orden del trazo y el relleno también afectan a los resultados.

El algoritmo de devanado depende de la dirección en la que se dibujan las líneas. Normalmente, al crear un trazado, puede controlar esa dirección a medida que especifique que las líneas se dibujan de un punto a otro. Sin embargo, la clase SKPath también define métodos como AddRect y AddCircle que dibujan contornos completos. Para controlar cómo se dibujan estos objetos, los métodos incluyen un parámetro de tipo SKPathDirection, que tiene dos miembros:

  • Clockwise
  • CounterClockwise

Los métodos de SKPath que incluyen un parámetro SKPathDirection le proporcionan un valor predeterminado de Clockwise.

La página Círculos superpuestos crea un trazado con cuatro círculos superpuestos con un tipo de relleno de trazado par-impar:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    SKPoint center = new SKPoint(info.Width / 2, info.Height / 2);
    float radius = Math.Min(info.Width, info.Height) / 4;

    SKPath path = new SKPath
    {
        FillType = SKPathFillType.EvenOdd
    };

    path.AddCircle(center.X - radius / 2, center.Y - radius / 2, radius);
    path.AddCircle(center.X - radius / 2, center.Y + radius / 2, radius);
    path.AddCircle(center.X + radius / 2, center.Y - radius / 2, radius);
    path.AddCircle(center.X + radius / 2, center.Y + radius / 2, radius);

    SKPaint paint = new SKPaint()
    {
        Style = SKPaintStyle.Fill,
        Color = SKColors.Cyan
    };

    canvas.DrawPath(path, paint);

    paint.Style = SKPaintStyle.Stroke;
    paint.StrokeWidth = 10;
    paint.Color = SKColors.Magenta;

    canvas.DrawPath(path, paint);
}

Es una imagen interesante creada con un mínimo de código:

Triple screenshot of the Overlapping Circles page