Compartir vía


Líneas y extremos de trazo

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

En SkiaSharp, representar una sola línea es muy diferente a representar una serie de líneas rectas conectadas. Sin embargo, incluso cuando se dibujan líneas solas, a menudo es necesario proporcionarles 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 extremo de la línea se denomina terminación del trazo:

Las tres opciones de límites de trazo

Para dibujar líneas solas, SKCanvas define un método DrawLine sencillo cuyos argumentos indican las coordenadas de inicio y fin de la línea con un objeto SKPaint:

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

De forma predeterminada, la propiedad StrokeWidth de un objeto SKPaint del que se ha creado una nueva instancia es 0, que tiene el mismo efecto que un valor de 1 en la representación de una línea con un grosor de un píxel. Este valor se ve muy fino en dispositivos de alta resolución, como los teléfonos, por lo que probablemente querrá establecer StrokeWidth en un valor mayor. Pero, una vez que empiece a dibujar líneas de un grosor considerable, surge otro problema: ¿cómo se deben representar los inicios y finales de estas líneas gruesas?

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

  • Butt (la opción predeterminada)
  • Square
  • Round

Estas se ilustran mejor con un programa de muestra. La secciónlíneas y rutas de skiaSharp del programa de ejemplo comienza con una página titulada trazo caps basada en la clase StrokeCapsPage . En esta página se define un controlador de eventos PaintSurface que recorre en bucle los tres miembros de la enumeración SKStrokeCap; se muestra el nombre del miembro de la enumeración y se dibuja una línea con esa terminación 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 enumeración SKStrokeCap, el controlador dibuja dos líneas, una con un grosor de trazo de 50 píxeles y la otra, situada en la parte superior, con un grosor de trazo de 2 píxeles. Esta segunda línea está pensada para ilustrar el inicio y el final geométricos de la línea independientemente del grosor de la línea y una terminación de trazo:

Captura de pantalla triple de la página Trazo

Como puede ver, las terminaciones de trazo Square y Round extienden la longitud de la línea en 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 clase SKCanvas también incluye otro método para dibujar varias líneas que es algo peculiar:

DrawPoints (SKPointMode mode, points, paint)

El parámetro points es una matriz de valores SKPoint y mode es miembro de la enumeración SKPointMode, 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 Líneas múltiples se muestra este método. El archivo MultipleLinesPage.xaml crea una instancia de dos vistas Picker que permiten seleccionar un miembro de la enumeración SKPointMode y un miembro de la enumeración SKStrokeCap:

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

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

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

Este controlador debe comprobar la existencia del objeto SKCanvasView porque primero se llama al controlador de eventos cuando la propiedad SelectedIndex de Picker está establecida en 0 en el archivo XAML, y eso ocurre antes de crear una instancia de SKCanvasView.

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

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 selecciones de Picker:

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

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

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

En cambio, cuando se 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 distintas comienza y termina con la terminación de línea especificada. Si selecciona las terminaciones Round, es posible que las líneas parezcan estar conectadas, pero realmente no lo están.

Que las líneas estén o no conectadas son un aspecto fundamental al trabajar con trazados gráficos.