路径填充类型

Download Sample下载示例

了解 SkiaSharp 路径填充类型可能产生的不同效果

路径中的两个轮廓可以重叠,构成单个轮廓的线条可以重叠。 可以填充任何封闭区域,但你可能不希望填充所有封闭区域。 下面是一个示例:

Five-pointed star partially filles

你可以对此进行一些控制。 填充算法由 SKPathSKFillType 属性控制,你将其设置为 SKPathFillType 枚举的成员:

  • Winding(默认值)
  • EvenOdd
  • InverseWinding
  • InverseEvenOdd

绕组和奇偶算法都根据从该区域绘制到无穷大的假设线来确定是否填充任何封闭区域。 该线跨越构成路径的一个或多个边界线。 使用绕组模式时,如果在一个方向上绘制的边界线数与另一个方向上绘制的线条数相抵,则不会填充该区域。 否则,则填充该区域。 如果边界线数为奇数,奇偶算法将填充区域。

对于许多常规路径,绕组算法通常会填充路径的所有封闭区域。 奇偶算法产生的结果通常更有趣。

经典示例是一个五角星,如“五角星”页面所示FivePointedStarPage.xaml 文件实例化两个 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.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>

代码隐藏文件使用两个 Picker 值来绘制五角星:

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

通常,路径填充类型应仅影响填充,而不影响描边,但两个 Inverse 模式将同时影响填充和描边。 对于填充,两个 Inverse 类型以相反方向填充区域,以便填充星形之外的区域。 对于描边,两个 Inverse 类型为描边之外的所有内容着色。 使用这些反向填充类型可能会产生一些奇怪的效果,如 iOS 屏幕截图所示:

Triple screenshot of the Five-Pointed Star page

Android 屏幕截图显示了典型的奇偶效果和绕组效果,但描边和填充的顺序也会影响结果。

绕组算法取决于绘制线条的方向。 通常,可以在创建路径时控制此方向,因为你指定的是从一个点到另一个点绘制线条。 但是,SKPath 类还定义绘制整个轮廓的 AddRectAddCircle 等方法。 为了控制这些对象的绘制方式,这些方法包括一个 SKPathDirection 类型的参数,其有两个成员:

  • Clockwise
  • CounterClockwise

SKPath 中包含 SKPathDirection 参数的方法为其指定默认值 Clockwise

“重叠圆圈”页面创建了包含四个重叠圆圈的路径,采用的是奇偶路径填充类型

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

这是一个使用最少的代码创建的有趣图像:

Triple screenshot of the Overlapping Circles page