線とストローク キャップ

Download Sampleサンプルのダウンロード

SkiaSharp を使用して、さまざまなストローク キャップで線を描画する方法の説明

SkiaSharp において、1 本の線のレンダリングは、一連の接続された複数の直線のレンダリングとは大きく異なります。 ただし、1 本の線を描画する場合でも、多くの場合、その線に特定のストローク幅を指定する必要があります。 これらの線が広くなるにつれて、線の端の外観も重要になります。 線の端の外観は、"ストローク キャップ" と呼ばれます。

The three stroke caps options

1 本の線を描画する場合、SKCanvas では単純な DrawLine メソッド (引数は線の始点および終点の座標と、 SKPaint オブジェクトを示します) を定義します。

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

既定では、新しくインスタンス化された SKPaint オブジェクトの StrokeWidth プロパティは 0 で、これは太さ 1 ピクセルの線をレンダリングする場合の値 1 と同じ効果があります。 これは携帯電話などの高解像度デバイス上では非常に細く表示されるため、StrokeWidth をより大きな値に設定することをお勧めします。 しかし、相当な太さの線を描画し始めると、これらの太い線の始点と終点をどうレンダリングするべきかという、別の問題が生じます。

線の始点や終点の外観は "ライン キャップ"、または Skia では "ストローク キャップ" と呼ばれます。 このコンテキストの中での "キャップ" という単語は、ある種の帽子 (線の端にあるもの) のことを指します。 SKPaint オブジェクトの StrokeCap プロパティに、SKStrokeCap 列挙型の次のメンバーのいずれかを設定します。

  • Butt (既定値)
  • Square
  • Round

これらは、サンプル プログラムで最もよく説明されています。 SkiaSharpFormsDemos プログラムの SkiaSharp Lines and Paths セクションは、StrokeCapsPage クラスに基づいた Stroke Caps というタイトルのページから始まります。 このページでは、SKStrokeCap 列挙型の 3 つのメンバーをループ処理する PaintSurface イベント ハンドラーを定義し、列挙型メンバーの名前と、そのストローク キャップを使用した線の描画の両方を表示します。

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

SKStrokeCap 列挙型の各メンバーについて、このハンドラーは 2 本の線を描画します。1 本はストロークの太さが 50 ピクセル、もう 1 本はストロークの太さが 2 ピクセルで上部に配置されます。 この 2 本目の線は、線の太さとストローク キャップに関係なく、線の幾何学的な始点と終点を示すことを目的としています。

Triple screenshot of the Stroke Caps page

ご覧のように、SquareRound のストローク キャップは線の始点と、また終点でも、ストローク幅の半分だけ線の長さを効果的に延長しています。 この延長は、レンダリングされたグラフィックス オブジェクトの寸法を決定する必要がある場合に重要になります。

SKCanvas クラスには、やや独特な複数の線を描画するための別のメソッドも含まれています。

DrawPoints (SKPointMode mode, points, paint)

points パラメーターは SKPoint 値の配列です。modeSKPointMode 列挙型のメンバーであり、次の 3 つのメンバーがあります。

  • Points: 個々のポイントをレンダリングします
  • Lines: ポイントの各ペアを接続します
  • Polygon: すべての連続するポイントを接続します

Multiple Lines Page ではこのメソッドを示します。 MultipleLinesPage.xaml ファイルは、SKPointMode 列挙型のメンバーと SKStrokeCap 列挙型のメンバーを選択できる 2 つの Picker ビューをインスタンス化します。

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

SkiaSharp 名前空間は SKPointMode および SKStrokeCap 列挙型のメンバーを参照するために必要であるため、SkiaSharp 名前空間の宣言は少し異なっていることに注意してください。 両方の Picker ビューの SelectedIndexChanged ハンドラーは、単に SKCanvasView オブジェクトを無効にします。

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

このハンドラーは SKCanvasView オブジェクトの存在をチェックする必要があります。それは、XAML ファイル内で PickerSelectedIndex プロパティに 0 が設定される際にこのイベント ハンドラーが最初に呼び出され、かつ SKCanvasView がインスタンス化される前にそれが実行されるためです。

PaintSurface ハンドラーは、Picker ビューから 2 つの列挙値を取得します。

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

このスクリーンショットにはさまざまな Picker の選択項目が示されています。

Triple screenshot of the Multiple Lines page

左側の iPhone は、ライン キャップが Butt または Square の場合に、SKPointMode.Points 列挙型メンバーによって DrawPointsSKPoint 配列内の各ポイントを正方形としてどのようにレンダリングするかを示しています。 ライン キャップが Round の場合は円がレンダリングされます。

Android のスクリーンショットは、SKPointMode.Lines の結果を示しています。 DrawPoints メソッドでは、指定されたライン キャップ (この場合は Round) を使用して、SKPoint 値の各ペアの間に線を描画します。

代わりに SKPointMode.Polygon を使用すると、配列内の連続するポイントの間に線が描画されます。しかし、よく見るとこれらの線が接続されていないことがわかります。 これらの個別の線はそれぞれ、始点と終点に指定したライン キャップが使用されます。 Round キャップを選択すると、その線は接続されているように見えますが、実際には接続されていません。

線が接続されているかどうかは、グラフィックス パスを操作する上できわめて重要な側面です。