文章目录
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;
}