文章目录
- SKPath 构造函数
- SKPath 属性
- Bounds 边界(宽边界)
- TightBounds紧边界
- FillType填充方式
- IsConcave 是否凹/ IsConvex 是否凸
- IsEmpty是否为空
- IsLine是否为线段
- IsRect是否为矩形
- IsOval是否为椭圆或圆
- IsRoundRect是否为圆角矩形
- Item[] 获取路径的坐标
- LastPoint最后点的坐标
- PointCount总共坐标点的个数
- Points获取所有坐标点
- SegmentMasks 路径包含的类型
- VerbCount动词个数
- 示例
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);}
- 创建一个空路径pathA,并添加一个圆.
- 创建另一个复制路径pathB
- 在pathA中再加入一个矩形。
- 在pathB中加入另一个圆。
- pathA与pathB互不影响。
克隆源路径有问题时,会抛出异常。
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);
}
- 通过四个点构造一段贝赛尔曲线,并构造一个独立点
- 验证Bounds的边界情况。
- 验证TightBounds的边界情况
- 红色为曲线,绿色为Bounds,紫色为TightBounds,蓝色为控制点。
TightBounds紧边界
public SkiaSharp.SKRect TightBounds { get; }
路径的实际边界。
FillType填充方式
public SkiaSharp.SKPathFillType FillType { get; set; }//默认是Winding
获取和设置路径的填充类型。
各枚举值的意义
-
Winding
(默认值):- 基于“非零缠绕数”规则来确定填充区域。
- 当路径穿过点的次数(方向考虑在内)不为零时,点在填充区域内。
-
EvenOdd
:- 基于“奇偶规则”来确定填充区域。
- 当路径穿过点的次数为奇数时,点在填充区域内;为偶数时,点在填充区域外。
-
InverseWinding
:- 与
Winding
类似,但填充区域是Winding
填充区域的反向。 - 当路径穿过点的次数(方向考虑在内)不为零时,点在填充区域外。
- 与
-
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++;}
}
- 定义一个生成五角星的函数
- 将画布分成四块,在每一块用不同的填充方式绘制五角星。
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);
}
绘制一个五角星和一个矩形,判断其凹凸性。
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。
示例
- 在路径中添加圆、矩形、椭圆、圆角矩形、画线段、二次/三次贝赛尔曲线
- 通过路径动词和坐标集来复制一个路径。
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;
}