【SkiaSharp绘图15】SKPath属性详解:边界、填充、凹凸、类型判断、坐标、路径类型

SKPath 构造函数

public class SKPath : SkiaSharp.SKObject

复合的几何路径,封装由直线段、二次曲线、三次组成的复合(多轮廓)几何路径

public SKPath ();//构造一个空路径
public SKPath (SkiaSharp.SKPath path);//深拷贝一个已存在的路径。

可构造一个空路径或深拷贝一个已存在的路径。

var canvas = e.Surface.Canvas;
canvas.Clear(SKColors.White);
using (var paint = new SKPaint())
using (var txtPaint = new SKPaint())
{
    paint.Color = SKColors.Red;
    paint.IsStroke = true;
    paint.IsAntialias = true;
    txtPaint.TextSize = 18;
    txtPaint.IsAntialias = true;
    txtPaint.Typeface = SKTypeface.FromFamilyName("宋体");

    var pathA=new SKPath();
    pathA.AddCircle(100, 100, 50);

    var pathB=new SKPath(pathA);//深拷贝

    pathA.AddRect(new SKRect(50, 200, 150, 300));

    canvas.DrawPath(pathA, paint);
    canvas.DrawText($"测试构造一个已存在路径时,深拷贝,还是浅拷贝。", 50, 400, txtPaint);

    canvas.Translate(300, 0);
    paint.Color = SKColors.Green;

    pathB.AddCircle(100,250,50);
    canvas.DrawPath(pathB, paint);

}
  1. 创建一个空路径pathA,并添加一个圆.
  2. 创建另一个复制路径pathB
  3. 在pathA中再加入一个矩形。
  4. 在pathB中加入另一个圆。
  5. pathA与pathB互不影响。
    SKPath构造函数
    克隆源路径有问题时,会抛出异常。

SKPath 属性

Bounds 边界(宽边界)

public SkiaSharp.SKRect Bounds { get; }

获取路径的边界矩形。如果路径为空,则返回空矩形。
注意些边界不是路径的真实边路,可能比实际边界大,因为其包含的是控制点或独立点(MoveTo)。

var canvas = e.Surface.Canvas;
canvas.Clear(SKColors.White);
using (var paint = new SKPaint())
using (var txtPaint = new SKPaint())
{
    paint.Color = SKColors.Red;
    paint.IsStroke = true;
    paint.IsAntialias = true;
    paint.TextSize = 36;

    var path = new SKPath();
    var pts = new SKPoint[]
    {
        new SKPoint(100,100),
        new SKPoint(200,200),
        new SKPoint(300,50),
        new SKPoint(400,250),
        new SKPoint(500,150)
    };
    //绘制一段贝赛尔曲线
    path.MoveTo(pts[0]);
    path.CubicTo(pts[1], pts[2], pts[3]);
    //构造一个独立点
    path.MoveTo(pts[4]);

    canvas.DrawPath(path, paint);

    var bounds = path.Bounds;
    if (bounds != SKRect.Empty)
    {
        paint.StrokeWidth = 5;
        paint.Color = SKColors.Green;
        canvas.DrawRect(bounds, paint);
        canvas.DrawText($"Bounds",bounds.Right,bounds.Top, paint);
    }

    var tightBounds = path.TightBounds;
    if (tightBounds != SKRect.Empty)
    {
        paint.StrokeWidth = 3;
        paint.Color = SKColors.Purple;
        canvas.DrawRect(tightBounds, paint);
        canvas.DrawText($"TightBounds", tightBounds.Right, tightBounds.Bottom, paint);
    }

    paint.StrokeWidth = 10;
    paint.StrokeCap = SKStrokeCap.Round;
    paint.Color = SKColors.Blue;
    canvas.DrawPoints(SKPointMode.Points, pts, paint);
}
  1. 通过四个点构造一段贝赛尔曲线,并构造一个独立点
  2. 验证Bounds的边界情况。
  3. 验证TightBounds的边界情况
  4. 红色为曲线,绿色为Bounds,紫色为TightBounds,蓝色为控制点。
    在这里插入图片描述

TightBounds紧边界

public SkiaSharp.SKRect TightBounds { get; }

路径的实际边界。

FillType填充方式

public SkiaSharp.SKPathFillType FillType { get; set; }//默认是Winding

获取和设置路径的填充类型。
各枚举值的意义

  1. Winding(默认值)

    • 基于“非零缠绕数”规则来确定填充区域。
    • 当路径穿过点的次数(方向考虑在内)不为零时,点在填充区域内。
  2. EvenOdd

    • 基于“奇偶规则”来确定填充区域。
    • 当路径穿过点的次数为奇数时,点在填充区域内;为偶数时,点在填充区域外。
  3. InverseWinding

    • Winding 类似,但填充区域是 Winding 填充区域的反向。
    • 当路径穿过点的次数(方向考虑在内)不为零时,点在填充区域外。
  4. InverseEvenOdd

    • EvenOdd 类似,但填充区域是 EvenOdd 填充区域的反向。
    • 当路径穿过点的次数为奇数时,点在填充区域外;为偶数时,点在填充区域内。
/// <summary>
/// 获取五星路径
/// </summary>
/// <returns></returns>
private SKPath GetStarPath(SKPoint center,float outerRadius)
{
    // 计算五角星的角度
    float angleStep = (float)(2 * Math.PI / 5);

    // 设置五角星的尺寸和位置
    //float outerRadius = 100;
    //SKPoint center = new SKPoint(150, 150); // 五角星中心点的位置

    // 计算五个顶点的坐标
    SKPoint[] starPoints = new SKPoint[5];
    for (int i = 0; i < 5; i++)
    {
        float angle = angleStep * i;
        // 外部顶点
        float x = center.X + outerRadius * (float)Math.Sin(angle);
        float y = center.Y - outerRadius * (float)Math.Cos(angle); // 减号确保顶点在中心上方
        starPoints[i] = new SKPoint(x, y);
    }
    var starPath=new SKPath();
    // 开始定义路径
    starPath.MoveTo(starPoints[0]); // 从第一个顶点开始

    int index2 = 0;
    // 连接相隔的两个顶点
    for (int i = 2; i <= 5; i ++)
    {
        index2 = (index2 + 2) % 5;
        starPath.LineTo(starPoints[index2]);
    }

    // 关闭路径
    starPath.Close();
    return starPath;
}
var canvas = e.Surface.Canvas;
canvas.Clear(SKColors.White);
var info = e.Info;
using (var paint = new SKPaint())
using (var txtPaint = new SKPaint())
{

    paint.IsAntialias = true;
    paint.TextAlign = SKTextAlign.Center;
    paint.StrokeWidth = 3;
    paint.TextSize = 24;

    var path = GetStarPath(new SKPoint(150, 150),100F);
    var fillTypes = Enum.GetValues(typeof(SKPathFillType)) as SKPathFillType[];

    float dx = info.Width / 2;
    float dy = info.Height / 2; ;
    var index = 0;
    //
    foreach (var fillType in fillTypes)
    {
        path.FillType = fillType;
        canvas.Save();
        canvas.Translate(dx * (index % 2), dy * (index / 2));
        canvas.ClipRect(SKRect.Create(dx, dy));
        paint.Color = SKColors.Red;
        canvas.DrawPath(path, paint);
        paint.Color = SKColors.Green;
        canvas.DrawText($"FillType:{fillType}", 150, 280, paint);               
        canvas.Restore();
        index++;
    }
}
  1. 定义一个生成五角星的函数
  2. 将画布分成四块,在每一块用不同的填充方式绘制五角星。

FillType

IsConcave 是否凹/ IsConvex 是否凸

public bool IsConcave { get; }
public bool IsConvex { get; }

判断一个路径的凹凸性。

  • 凹点(Convex Point):如果轮廓上的每个点都在其相邻两个点所形成的线段的同侧(包括线段上的端点),那么这个点就是凸的。
  • 凹点(Concave Point):如果轮廓上的某个点不满足凸点的定义,则称其为凹点。
var canvas = e.Surface.Canvas;
canvas.Clear(SKColors.White);
var info = e.Info;
using (var paint = new SKPaint())
{
    paint.IsAntialias = true;
    paint.StrokeWidth = 3;
    paint.TextSize = 24;
    paint.Color = SKColors.Red;
    paint.Typeface = SKTypeface.FromFamilyName("宋体");

    var pathA = GetStarPath(new SKPoint(150, 150), 100);
    var pathB = new SKPath();
    pathB.AddRect(new SKRect(300, 50, 500, 200));

    canvas.DrawPath(pathA, paint);
    canvas.DrawText($"IsConcave凹:{pathA.IsConcave}", 20, 280, paint);
    canvas.DrawText($"IsConvex 凸:{pathA.IsConvex}", 20, 320, paint);

    canvas.DrawPath(pathB, paint);
    canvas.DrawText($"IsConcave凹:{pathB.IsConcave}", 320, 280, paint);
    canvas.DrawText($"IsConvex 凸:{pathB.IsConvex}", 320, 320, paint);
}

绘制一个五角星和一个矩形,判断其凹凸性。
IsConcave/IsConvex

IsEmpty是否为空

public bool IsEmpty { get; }

判断一个路径是否不包含任何线段或曲线。

IsLine是否为线段

public bool IsLine { get; }

判断一个路径是否为一条线段。

IsRect是否为矩形

public bool IsRect { get; }

判断路径是否为矩形。
注意,不是水平、垂直相交相连的线组成的矩形,这里返回的结果还是false。(是Skia的Bug吗?)

IsOval是否为椭圆或圆

public bool IsOval { get; }

判断路径是否为椭圆或圆。
注意四段贝赛尔曲线构成的圆,这里返回的结果还是false。

IsRoundRect是否为圆角矩形

public bool IsRoundRect { get; }

判断路径是否为圆角矩形。

** 相关示例 **

var canvas = e.Surface.Canvas;
canvas.Clear(SKColors.White);
var info = e.Info;
using (var paint = new SKPaint())
{
    paint.IsAntialias = true;
    paint.StrokeWidth = 3;
    paint.TextSize = 24;
    paint.Color = SKColors.Red;
    paint.Typeface = SKTypeface.FromFamilyName("宋体");

    var pathA = new SKPath();
    var offsetY = 30F;
    var offsetX = 150F;
    canvas.DrawText($"1.new SKPath() IsEmpty:{pathA.IsEmpty}", offsetX, offsetY, paint);                
    offsetY += paint.FontSpacing;
    pathA.MoveTo(50, 50);
    canvas.DrawText($"2.1 MoveTo(50, 50) IsEmpty:{pathA.IsEmpty}", offsetX, offsetY, paint);
    offsetY += paint.FontSpacing;

    canvas.DrawText($"2.1 MoveTo(50, 50) IsLine: {pathA.IsLine}", offsetX, offsetY, paint);
    offsetY += paint.FontSpacing;

    pathA.LineTo(100, 50);
    canvas.DrawText($"3 LineTo(100, 50 IsLine: {pathA.IsLine}", offsetX, offsetY, paint);
    offsetY += paint.FontSpacing;

    pathA.LineTo(100, 100);
    pathA.LineTo(50, 100);
    pathA.Close();
    canvas.DrawText($"4 LineTo(100, 100),LineTo(50, 100),Close() IsRect: {pathA.IsRect}", offsetX, offsetY, paint);

    canvas.DrawPath(pathA, paint);

    var pathB=new SKPath();
    pathB.MoveTo(100, 150);
    pathB.LineTo(150, 200);
    pathB.LineTo(100, 250);
    pathB.LineTo(50, 200);
    pathB.Close();
    paint.IsStroke = true;
    canvas.DrawPath(pathB, paint);

    var width = (float)(Math.Sqrt(2) * 50);

    offsetY += 100;
    canvas.DrawText($"pathB IsRect: {pathB.IsRect}", offsetX, offsetY, paint);
    offsetY += paint.FontSpacing;
    var pathC=new SKPath();

    var rect = new SKRect(0, 0, width, width);
    paint.IsStroke = false;
    pathC.AddRect(rect);
    paint.Color = SKColors.LightGreen.WithAlpha(128);
    canvas.DrawText($"与pathB相同大小的pathC IsRect: {pathC.IsRect}", offsetX, offsetY, paint);

    canvas.Save();
    canvas.Translate(100, 150);
    canvas.RotateDegrees(45F);

    canvas.DrawPath(pathC, paint);
    canvas.Restore();

    var pathD=new SKPath();
    pathD.AddCircle(100, 350, 50);
    paint.IsStroke = true;
    canvas.DrawPath(pathD, paint);
    offsetY += 100;
    canvas.DrawText($"pathD IsOval: {pathD.IsOval}", offsetX, offsetY, paint);
    offsetY += paint.FontSpacing;

    var pathE = CreateCircleWithBeziers(100, 350, 50);
    paint.Color = SKColors.LightBlue.WithAlpha(128);
    paint.IsStroke = false;
    canvas.DrawPath(pathE, paint);
    canvas.DrawText($"贝赛尔曲线构成的圆 pathE IsOval: {pathE.IsOval}", offsetX, offsetY, paint);
    offsetY += 100;

    paint.Color = SKColors.LightBlue;
    canvas.DrawText($"pathC IsRoundRect: {pathE.IsRoundRect}", offsetX, offsetY, paint);
    offsetY += paint.FontSpacing;
    var pathF = new SKPath();
    pathF.AddRoundRect(new SKRect(30, 450, 150, 500), 10, 20);
    canvas.DrawPath(pathF, paint);
    canvas.DrawText($"pathF IsRoundRect: {pathF.IsRoundRect}", offsetX, offsetY, paint);
}

四条贝赛尔构成的圆

/// <summary>
/// 四条贝赛尔曲线构成的圆
/// </summary>
/// <param name="centerX"></param>
/// <param name="centerY"></param>
/// <param name="radius"></param>
/// <returns></returns>
public SKPath CreateCircleWithBeziers(float centerX, float centerY, float radius)
{
    // 定义路径
    var path = new SKPath();

    // 贝塞尔曲线控制点的系数
    float c = (float)(radius * (4.0f / 3.0f) * (Math.Sqrt(2) - 1));

    // 定义起点
    path.MoveTo(centerX + radius, centerY);

    // 第一段贝塞尔曲线
    path.CubicTo(centerX + radius, centerY + c, centerX + c, centerY + radius, centerX, centerY + radius);

    // 第二段贝塞尔曲线
    path.CubicTo(centerX - c, centerY + radius, centerX - radius, centerY + c, centerX - radius, centerY);

    // 第三段贝塞尔曲线
    path.CubicTo(centerX - radius, centerY - c, centerX - c, centerY - radius, centerX, centerY - radius);

    // 第四段贝塞尔曲线
    path.CubicTo(centerX + c, centerY - radius, centerX + radius, centerY - c, centerX + radius, centerY);

    // 闭合路径
    path.Close();

    return path;
}

路径形状判断

Item[] 获取路径的坐标

public SkiaSharp.SKPoint this[int index] { get; }

获取路径上指定序号的点的坐标。如果序号超出小于0或大于等于PointCount,则返回(0,0)。

LastPoint最后点的坐标

public SkiaSharp.SKPoint LastPoint { get; }

获取路径的最后一个点的坐标。如果路径为空,返回(0,0)。

PointCount总共坐标点的个数

public int PointCount { get; }

获取路径所有坐标点的个数。

Points获取所有坐标点

public SkiaSharp.SKPoint[] Points { get; }

获取路径所有坐标点。注意,这些点包含控制点,即不一定在路径轮廓上。

SegmentMasks 路径包含的类型

public SkiaSharp.SKPathSegmentMask SegmentMasks { get; }

获取一组标志,指示路径是否包含该类型的一个或多个段。

说明
Line =1线段(2个点)
Quad=2二次贝赛尔曲线(3个点)
Cubic=8三次贝赛尔曲线(4个点)
Conic=4圆锥曲线(3个点+权重)

VerbCount动词个数

public int VerbCount { get; }

获取路径中动词的数量(即SKPathVerb,有Move、Line、Quad、Conic、Cubic、Close、Done)
用SKPath对象的CreateRawIterator可获取相关SKPathVerb。

示例

  1. 在路径中添加圆、矩形、椭圆、圆角矩形、画线段、二次/三次贝赛尔曲线
  2. 通过路径动词和坐标集来复制一个路径。
var canvas = e.Surface.Canvas;
canvas.Clear(SKColors.White);
var info = e.Info;
using (var paint = new SKPaint())
{
    paint.IsAntialias = true;
    paint.StrokeWidth = 7;
    paint.IsStroke = true;
    paint.TextSize = 24;
    paint.Color = SKColors.Red;
    paint.Typeface = SKTypeface.FromFamilyName("宋体");

    var path=new SKPath();
    path.AddCircle(200, 200, 100);

    path.AddRect(new SKRect(350, 100, 550, 200));

    path.AddOval(new SKRect(100, 350, 300, 450));

    path.AddRoundRect(new SKRoundRect(new SKRect(400,350,700,500),30));

    path.LineTo(300, 600);

    canvas.DrawPath(path, paint);

    var pts = path.Points;

    var verbCount = path.VerbCount;
    paint.Color = SKColors.Blue;
    canvas.DrawPoints(SKPointMode.Points, pts, paint);

    paint.StrokeWidth = 2;
    var drawPtList=new List<SKPoint>();
    for (int i = 0; i < pts.Length; i++)
    {
        var pt = pts[i];
        var count = drawPtList.Count(z => z.Equals(pt));
        if (count > 0)
        {
            var color = paint.Color;
            paint.Color = SKColors.Green;
            canvas.DrawText($"{i}", new SKPoint(pt.X - paint.TextSize * count, pt.Y), paint);
            paint.Color = color;
        }
        else
        {
            canvas.DrawText($"{i}", pts[i], paint);
        }
        drawPtList.Add(pt);
    }

    paint.Color = SKColors.Green;

    var newPath = ClonePathByVerb(path);

    paint.StrokeWidth = 1;
    canvas.DrawPath(newPath, paint);

    var offsetX = 20F;
    var offsetY = 500F;
    canvas.DrawText($"Path.PointCount:{path.PointCount}", offsetX, offsetY, paint);
    offsetY += paint.FontSpacing;
    canvas.DrawText($"Path.SegmentMasks:{path.SegmentMasks}", offsetX, offsetY, paint);
    offsetY += paint.FontSpacing;
    canvas.DrawText($"Path.VerbCount:{path.VerbCount}", offsetX, offsetY, paint);
    offsetY += paint.FontSpacing;
    canvas.DrawText($"Path.LastPoint:{path.LastPoint}", offsetX, offsetY, paint);
    offsetY += paint.FontSpacing;
}

SKPath属性

  • 16
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

图南科技

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值