Líneas y extremos de trazo

Descargar ejemploDescargar el ejemplo

Aprenda a usar SkiaSharp para dibujar líneas con diferentes tapas de trazo

En SkiaSharp, la representación de una sola línea es muy diferente de representar una serie de líneas rectas conectadas. Sin embargo, incluso cuando se dibujan líneas únicas, a menudo es necesario dar a las líneas un ancho de trazo determinado. A medida que estas líneas se vuelven más anchas, la apariencia de los extremos de las líneas también es importante. La apariencia del final de la línea se denomina límite de trazo:

Las tres opciones de límites de trazo

Para dibujar líneas simples, SKCanvas define un método simple DrawLine cuyos argumentos indican las coordenadas inicial y final de la línea con un SKPaint objeto :

canvas.DrawLine (x0, y0, x1, y1, paint);

De forma predeterminada, la StrokeWidth propiedad de un objeto recién creado SKPaint es 0, que tiene el mismo efecto que un valor de 1 en la representación de una línea de un píxel en grosor. Esto aparece muy fino en dispositivos de alta resolución, como teléfonos, por lo que probablemente querrá establecer en StrokeWidth un valor mayor. Pero una vez que empiece a dibujar líneas de un grosor considerable, esto provoca otro problema: ¿Cómo deben representarse los inicios y extremos de estas líneas gruesas?

La apariencia de los inicios y extremos de las líneas se denomina extremo de línea o, en Skia, un extremo de trazo. La palabra "cap" en este contexto hace referencia a un tipo de sombrero, algo que se encuentra al final de la línea. La propiedad del SKPaint objeto se establece StrokeCap en uno de los siguientes miembros de la SKStrokeCap enumeración:

  • Butt (valor predeterminado)
  • Square
  • Round

Se ilustran mejor con un programa de ejemplo. La sección Líneas y rutas de skiaSharp del programa SkiaSharpFormsDemos comienza con una página titulada Stroke Caps basada en la StrokeCapsPage clase . En esta página se define un PaintSurface controlador de eventos que recorre en bucle los tres miembros de la SKStrokeCap enumeración, mostrando el nombre del miembro de enumeración y dibujando una línea con ese límite de trazo:

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

    canvas.Clear();

    SKPaint textPaint = new SKPaint
    {
        Color = SKColors.Black,
        TextSize = 75,
        TextAlign = SKTextAlign.Center
    };

    SKPaint thickLinePaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Orange,
        StrokeWidth = 50
    };

    SKPaint thinLinePaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Black,
        StrokeWidth = 2
    };

    float xText = info.Width / 2;
    float xLine1 = 100;
    float xLine2 = info.Width - xLine1;
    float y = textPaint.FontSpacing;

    foreach (SKStrokeCap strokeCap in Enum.GetValues(typeof(SKStrokeCap)))
    {
        // Display text
        canvas.DrawText(strokeCap.ToString(), xText, y, textPaint);
        y += textPaint.FontSpacing;

        // Display thick line
        thickLinePaint.StrokeCap = strokeCap;
        canvas.DrawLine(xLine1, y, xLine2, y, thickLinePaint);

        // Display thin line
        canvas.DrawLine(xLine1, y, xLine2, y, thinLinePaint);
        y += 2 * textPaint.FontSpacing;
    }
}

Para cada miembro de la SKStrokeCap enumeración, el controlador dibuja dos líneas, una con un grosor de trazo de 50 píxeles y otra línea situada en la parte superior con un grosor de trazo de dos píxeles. Esta segunda línea está pensada para ilustrar el inicio geométrico y el final de la línea independientemente del grosor de la línea y un extremo de trazo:

de trazos Captura de pantalla triple de la página Límites de trazos

Como puede ver, los Square límites de trazo y Round extienden eficazmente la longitud de la línea por la mitad del ancho del trazo al principio de la línea y de nuevo al final. Esta extensión es importante cuando es necesario determinar las dimensiones de un objeto gráfico representado.

La SKCanvas clase también incluye otro método para dibujar varias líneas que es algo peculiar:

DrawPoints (SKPointMode mode, points, paint)

El points parámetro es una matriz de SKPoint valores y mode es miembro de la SKPointMode enumeración , que tiene tres miembros:

  • Points para representar los puntos individuales
  • Lines para conectar cada par de puntos
  • Polygon para conectar todos los puntos consecutivos

En la página Varias líneas se muestra este método. El archivo MultipleLinesPage.xaml crea una instancia de dos Picker vistas que permiten seleccionar un miembro de la SKPointMode enumeración y un miembro de la SKStrokeCap enumeración:

<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.MultipleLinesPage"
             Title="Multiple Lines">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Picker x:Name="pointModePicker"
                Title="Point Mode"
                Grid.Row="0"
                Grid.Column="0"
                SelectedIndexChanged="OnPickerSelectedIndexChanged">
            <Picker.ItemsSource>
                <x:Array Type="{x:Type skia:SKPointMode}">
                    <x:Static Member="skia:SKPointMode.Points" />
                    <x:Static Member="skia:SKPointMode.Lines" />
                    <x:Static Member="skia:SKPointMode.Polygon" />
                </x:Array>
            </Picker.ItemsSource>
            <Picker.SelectedIndex>
                0
            </Picker.SelectedIndex>
        </Picker>

        <Picker x:Name="strokeCapPicker"
                Title="Stroke Cap"
                Grid.Row="0"
                Grid.Column="1"
                SelectedIndexChanged="OnPickerSelectedIndexChanged">
            <Picker.ItemsSource>
                <x:Array Type="{x:Type skia:SKStrokeCap}">
                    <x:Static Member="skia:SKStrokeCap.Butt" />
                    <x:Static Member="skia:SKStrokeCap.Round" />
                    <x:Static Member="skia:SKStrokeCap.Square" />
                </x:Array>
            </Picker.ItemsSource>
            <Picker.SelectedIndex>
                0
            </Picker.SelectedIndex>
        </Picker>

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

Observe que las declaraciones de espacio de nombres SkiaSharp son un poco diferentes porque el SkiaSharp espacio de nombres es necesario para hacer referencia a los miembros de las SKPointMode enumeraciones y SKStrokeCap . El SelectedIndexChanged controlador de ambas Picker vistas simplemente invalida el SKCanvasView objeto :

void OnPickerSelectedIndexChanged(object sender, EventArgs args)
{
    if (canvasView != null)
    {
        canvasView.InvalidateSurface();
    }
}

Este controlador debe comprobar la existencia del SKCanvasView objeto porque primero se llama al controlador de eventos cuando la SelectedIndex propiedad de Picker se establece en 0 en el archivo XAML y se produce antes SKCanvasView de que se haya creado una instancia de .

El PaintSurface controlador obtiene los dos valores de enumeración de las Picker vistas:

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

    canvas.Clear();

    // Create an array of points scattered through the page
    SKPoint[] points = new SKPoint[10];

    for (int i = 0; i < 2; i++)
    {
        float x = (0.1f + 0.8f * i) * info.Width;

        for (int j = 0; j < 5; j++)
        {
            float y = (0.1f + 0.2f * j) * info.Height;
            points[2 * j + i] = new SKPoint(x, y);
        }
    }

    SKPaint paint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.DarkOrchid,
        StrokeWidth = 50,
        StrokeCap = (SKStrokeCap)strokeCapPicker.SelectedItem
    };

    // Render the points by calling DrawPoints
    SKPointMode pointMode = (SKPointMode)pointModePicker.SelectedItem;
    canvas.DrawPoints(pointMode, points, paint);
}

Las capturas de pantalla muestran una variedad de Picker selecciones:

Captura de pantalla triple de la página Varias líneas

El iPhone de la izquierda muestra cómo el SKPointMode.Points miembro de enumeración hace DrawPoints que cada uno de los puntos de la SKPoint matriz sea un cuadrado si el límite de línea es Butt o Square. Los círculos se representan si el límite de línea es Round.

En la captura de pantalla de Android se muestra el resultado de SKPointMode.Lines. El DrawPoints método dibuja una línea entre cada par de valores, utilizando el límite de SKPoint línea especificado, en este caso Round.

Cuando en su lugar usa SKPointMode.Polygon, se dibuja una línea entre los puntos sucesivos de la matriz, pero si observa muy detenidamente, verá que estas líneas no están conectadas. Cada una de estas líneas independientes comienza y termina con el límite de línea especificado. Si selecciona los Round límites, es posible que parezca que las líneas están conectadas, pero realmente no están conectadas.

Tanto si las líneas están conectadas como si no están conectadas es un aspecto crucial de trabajar con rutas de gráficos.