Path Overview
Path 是 2D 矢量图形的基础构建工具。它包含了几何体(Geometry)的轮廓信息,可能为空,也可能由一组描述线条、曲线的曲线命令(Verbs)和控制点(Points)组成。在路径的构建中,tgfx 支持直线(Line)、二次贝塞尔曲线(Quad)、三次贝塞尔曲线(Cubic)以及圆锥曲线(Conic)等多种线型。
同时,Path 本身携带填充规则(Fill Type)属性,支持 Winding(非零环绕规则)、EvenOdd(奇偶规则)及其相应的 Inverse(反转)模式,这决定了在路径发生重叠或自交时,哪些区域被判定为“内部”。
通过调用 Canvas 的 drawPath() 等绘制方法,并结合 Paint 设定的填充(Fill)或描边(Stroke)样式,可以将构建好的 Path 渲染出任意复杂的二维图形。
1. 路径构建 (Path Construction)
Path 由一组连续的曲线命令(PathVerb)和控制点构成,形成一个或多个轮廓(Contour)。每一次 moveTo 都会开启一个新的轮廓,而后续的线条和曲线命令会将新的坐标点连接到当前轮廓上。
核心构建方法
#include "tgfx/core/Path.h"
#include "tgfx/core/Point.h"
tgfx::Path path;
// 1. moveTo:将画笔移动到起点 (10, 10),开启新轮廓
path.moveTo(10.0f, 10.0f);
// 2. lineTo:从当前点连一条直线到 (50, 10)
path.lineTo(50.0f, 10.0f);
// 3. quadTo:添加二阶贝塞尔曲线,控制点 (100, 10),终点 (100, 50)
path.quadTo(100.0f, 10.0f, 100.0f, 50.0f);
// 4. cubicTo:添加三阶贝塞尔曲线,两个控制点,终点 (150, 100)
path.cubicTo(100.0f, 80.0f, 120.0f, 100.0f, 150.0f, 100.0f);
// 5. conicTo:添加圆锥曲线(带有权重 weight),权重为 1 等价于 quadTo
path.conicTo(180.0f, 100.0f, 180.0f, 150.0f, 0.707f);
// 6. close:闭合当前轮廓(从当前点连一条直线回到起点)
path.close();
控制点图解
以下是五种路径构建命令的控制点原理图解:
lineTo: 包含 1 个终点。quadTo: 包含 1 个控制点、1 个终点。cubicTo: 包含 2 个控制点、1 个终点。conicTo: 包含 1 个控制点、1 个终点,外加一个weight(权重参数,weight < 1为椭圆弧,weight == 1为抛物线且等价于二次贝塞尔曲线,weight > 1为双曲线)。
2. 几何体快捷方法 (Geometry Shortcuts)
如果你需要绘制标准的几何图形,Path 提供了一系列 addXXX 快捷方法。
tgfx::Path path;
// 添加矩形 (Rect)
tgfx::Rect rect = tgfx::Rect::MakeXYWH(10, 10, 100, 50);
path.addRect(rect);
// 添加圆角矩形 (RoundRect)
// radiusX 和 radiusY 控制圆角大小
path.addRoundRect(rect, 10.0f, 10.0f);
// 添加椭圆 (Oval)。快捷技巧:传入一个正方形的 Rect 即可生成完美圆形。
path.addOval(rect);
// 添加椭圆弧段 (Arc)
// startAngle 为起始角度(0度在X轴正方向),sweepAngle 为扫过的角度(正数为顺时针)
path.addArc(rect, 0.0f, 90.0f);
// 追加其他路径
tgfx::Path anotherPath;
anotherPath.addRect(tgfx::Rect::MakeWH(50, 50));
path.addPath(anotherPath); // 默认使用 Append 模式将顶点数据追加进来
3. 填充规则 (Fill Rules)
PathFillType 决定了当路径存在自交,或者包含了多个相交的子轮廓时,哪些区域属于“内部”(被填充),哪些区域属于“外部”(被掏空)。
// 默认的填充规则是 Winding
tgfx::PathFillType type = path.getFillType();
// 更改填充规则为 EvenOdd
path.setFillType(tgfx::PathFillType::EvenOdd);
1. Winding (非零环绕规则)

2. EvenOdd (奇偶规则)

3. Inverse (反转填充)

Winding(非零环绕数规则):如果画一条从该区域指向无穷远处的射线,顺时针穿过射线加1,逆时针穿过射线减1。如果最终环绕数为非零值,则该区域被填充。EvenOdd(奇偶规则):如果从该区域指向无穷远处的射线穿过路径的次数为奇数,则该区域被填充。这通常会在路径自交的地方产生“镂空”效果。InverseWinding:Winding的反转结果(填充外部区域)。InverseEvenOdd:EvenOdd的反转结果。
4. 路径变换与布尔运算 (Transform & Boolean Operations)
路径变换
通过 transform 方法,可以对路径的所有顶点进行矩阵变换。这会直接改变 Path 内部的数据。
#include "tgfx/core/Matrix.h"
tgfx::Path path;
path.addRect(tgfx::Rect::MakeWH(100, 100));
// 创建一个平移并缩放的矩阵
tgfx::Matrix matrix;
matrix.postTranslate(50.0f, 50.0f);
matrix.postScale(2.0f, 2.0f);
// 对当前路径应用矩阵变换
path.transform(matrix);
// 注意:如果想合并一条经过变换的路径,可以先将源路径 transform,再调用 addPath
tgfx::Path srcPath;
srcPath.addOval(tgfx::Rect::MakeWH(50, 50));
srcPath.transform(matrix);
path.addPath(srcPath, tgfx::PathOp::Append);
布尔运算
addPath 提供了一个强大的可选参数 PathOp,可以对两条路径执行复杂的布尔运算,生成新的联合路径(内部会自动处理多边形切割和交叉点合并)。
tgfx::Path circlePath;
circlePath.addOval(tgfx::Rect::MakeXYWH(0, 0, 100, 100));
tgfx::Path rectPath;
rectPath.addRect(tgfx::Rect::MakeXYWH(50, 50, 100, 100));
// 1. Union:合并两个图形,去除内部重叠线段
circlePath.addPath(rectPath, tgfx::PathOp::Union);
// 2. Intersect:仅保留两个图形相交的部分
circlePath.addPath(rectPath, tgfx::PathOp::Intersect);
// 3. Difference:从原路径中减去 rectPath 重叠的部分
circlePath.addPath(rectPath, tgfx::PathOp::Difference);
// 4. XOR:保留两图形总面积,但挖去相交的重叠部分
circlePath.addPath(rectPath, tgfx::PathOp::XOR);
5. 路径查询 (Path Queries)
Path 提供了一组状态查询方法,方便快速判定几何体特征,以便进行绘制优化或碰撞检测。
提示:底层几何特征判定机制大揭秘 不同的
isXXX方法底层的判断机制存在根本差异,了解这点能避免很多隐蔽的 Bug:
isRect():基于顶点拓扑计算。无论是由addRect生成,还是手动用 4 条线段闭合拼接的矩形,只要数学上等价就会返回true。isOval()/isRRect():基于内部标记位判定。对任意曲线进行椭圆或圆角矩形的等效推断成本过高,所以只有通过addOval()/addRoundRect()等专门的快捷 API 构造的路径才会返回true,手动使用贝塞尔曲线完美拼接出来的也是无法被识别的。isLine():基于严格的指令树结构。并不是判断所有点是否共线,而是检查整个路径是否仅仅包含一个moveTo加一个lineTo。就算你用三段共线的lineTo拼出一条线段,这里也会返回false。
tgfx::Path path;
// 1. 获取包围盒:返回能包围路径所有控制点的最小矩形,常用于快速相交测试(视口剔除等)
tgfx::Rect bounds = path.getBounds();
// 2. 几何特征判定
bool isLine = path.isLine(); // 是否刚好只有一条连续的线段(仅含 1 个 moveTo 和 1 个 lineTo)
bool isRect = path.isRect(); // 是否等价于一个矩形(拓扑推断)
bool isOval = path.isOval(); // 是否是一个椭圆或圆(标记位推断)
bool isRRect = path.isRRect(); // 是否是一个圆角矩形(标记位推断)
// 3. 状态查询
bool empty = path.isEmpty(); // 路径内是否包含任何有效顶点
// 4. 点/矩形包含测试:判定给定的坐标是否落在路径所形成的填充区域内(支持复杂的 Winding/EvenOdd 计算)
bool containsPoint = path.contains(50.0f, 50.0f);
bool containsRect = path.contains(tgfx::Rect::MakeXYWH(10, 10, 5, 5));
