首页 / 资讯中心 / 文章详情

Skia SkPath 详解:Contour、Verb、FillType 与 Copy-on-Write 的完整指南

Skia SkPath 详解:Contour、Verb、FillType 与 Copy-on-Write 的完整指南 ★ FEATURED ARTICLE
图形学图像处理【免费下载链接】skiaSkia is a complete 2D graphic library for drawing Text, Geometries, and Images.项目地址https://gitcode.com/gh_mirrors/skia1/skia点击查看免费下载本指南以 Skia 官方用户文档 SkPath Overview 为骨架系统讲解 SkPath 的核心概念——由 Line 与 Curve 构成的 Contour、描述几何的 Verb 与 Point 序列、决定填充区域的 FillType以及按值拷贝时的写时复制copy-on-write机制。你将理解 Path 内部的数据组织方式、闭合/开放 Contour 对描边stroke与填充fill的影响并能据此写出正确、高效的路径构建代码。1. SkPath 是什么线、曲线与轮廓的组合体Skia 是一个完整的 2D 图形库而 SkPath 是其几何描述的核心类型。在 Skia 中Path 包含Line直线与Curve曲线这些线段与曲线可以被描边stroked或填充filled。Contour轮廓由一系列首尾相连的 Line 与 Curve 组成。一个 Path 可以包含零个、一个或多个 Contour。每个 Line 与 Curve 都由三类数据描述Verb动词告诉 SkPath 如何解释后面的点例如移动到画直线画二次曲线Point点即 SkPoint 序列提供几何坐标可选 Conic Weight圆锥曲线权重仅在描述圆锥曲线conic时出现。从源码看这一结构与 SkPath::Verb 枚举 及 SkPathVerb 一一对应enum class SkPathVerb { kMove, // SkPath::RawIter 返回 1 个点 kLine, // SkPath::RawIter 返回 2 个点 kQuad, // SkPath::RawIter 返回 3 个点 kConic, // SkPath::RawIter 返回 3 个点 1 个权重 kCubic, // SkPath::RawIter 返回 4 个点 kClose // SkPath::RawIter 返回 0 个点 };1.1 相邻线段共享公共点每条相连的 Line/Curve 会共享公共 Point。官方文档给出的例子一个包含两条相连 Line 的 Path其 Verb 序列为kMove_Verb, kLine_Verb, kLine_Verb对应的 Point 序列包含三个条目中间条目被共享——它既是第一条 Line 的终点又是第二条 Line 的起点。这种点复用设计使 Path 的数据紧凑、无冗余也让后续的路径操作如变换、求交更加高效。1.2 复杂几何组件只是 Line 与 Curve 的合成Arc圆弧、Rect矩形、Round_Rect圆角矩形、Circle圆、Oval椭圆等高级组件在加入 Path 后本质上都被分解为所需数量的 Line 与 Curve。文档明确指出一旦加入 Path这些组件可能失去其身份不过 Path 仍可被检查以判断它是否恰好描述了一个 Rect、Oval、Round_Rect 等。这意味着 Skia 在内部做了反向识别例如一个由四条 Line 首尾相连、方向一致的闭合 ContourisRect()等查询方法仍能把它识别为矩形参见 SkPath.h 中的相关查询接口。这为后续的快速裁剪clip与绘制优化提供了可能。2. ContourPath 的基本组织单元Contour包含一个或多个 Verb以及满足 Verb 数组所需数量的 Point。Contour 的规则非常明确Path 中的第一个 Verb 永远是kMove_Verb每一个后续的kMove_Verb都会开启一个新的 Contour。因此Contour 的边界完全由moveTo()与close()划分。官方示例fiddle0374f2dcd7effeb1dd435205a6c2de6f说明了两个要点每次moveTo都会开启新 Contourclose()之后的内容同样会开启新 Contour如果某个绘制命令如conicTo前面没有moveTo那么新 Contour 的第一个点会接续上一个 Contour 的最后一点。2.1 闭合 Contour 与开放 Contour如果 Contour 的最后一个 Verb 是kClose_Verb那么一条Line 会把 Contour 的 Last Point 与第一个 Point 连接起来Contour 由此闭合闭合 Contour 描边在 Last Point 与第一个 Point 处绘制Paint_Stroke_Join描边连接样式如斜角bevel、圆角round或尖角miter开放 Contour 描边如果没有kClose_Verb收尾Last Point 与第一个 Point 不连接Contour 保持开放描边时在这两个端点绘制Paint_Stroke_Cap描边端点样式如平头butt、圆头round或方头square。在实际代码中闭合由 SkPath::close() 产生SkPath path; path.moveTo(10, 10); path.lineTo(100, 10); path.lineTo(50, 80); path.close(); // 自动连接 (50,80) 与 (10,10)形成闭合三角形close()的语义在头文件中有明确约束如果 Path 为空或最后一个 Verb 已是kClose_Verb则close()不产生任何效果见 SkPath.h 注释。2.2 零长度 Contour描边依然可见Contour 的长度等于从第一个 Point 到 Last Point 的移动距离若 Contour 闭合还要加上从 Last Point 回到第一个 Point 的距离。文档特别强调一个反直觉的结论即使 Contour 长度为 0只要Paint_Stroke_Cap使其可见描边出的 Line 依然会被绘制。这是零长度线段 圆头/方头端点产生可见圆点/方点的经典场景被广泛用于绘制点标记。若想真正不绘制需要配合空 Path 或显式跳过绘制逻辑。3. Verb 与 PointPath 的内部语言要深入理解 Path必须看懂它的内部语言——Verb 数组决定语义Point 数组提供坐标Conic Weight 数组补充圆锥曲线信息。3.1 每个 Verb 对应的点数在 SkPathTypes.h 中每个 Verb 与 RawIter原始迭代器返回的点数有固定对应关系Verb语义需要的 Point 数kMove移动到新位置开启新 Contour1kLine从当前点画直线到目标点2含起始点kQuad二次贝塞尔曲线3起点 控制点 终点kConic带权重的圆锥曲线3 1 个权重kCubic三次贝塞尔曲线4起点 两个控制点 终点kClose闭合当前 Contour03.2 构建 Path 的完整命令集对应的构建命令全部返回SkPath以便链式调用且都支持绝对坐标与相对坐标两种形式moveTo(x, y) /rMoveTo(dx, dy)开启新 ContourlineTo(x, y) /rLineTo(dx, dy)画直线quadTo(x1, y1, x2, y2)画二次贝塞尔曲线conicTo(x1, y1, x2, y2, w) /rConicTo(...)画带权重的圆锥曲线cubicTo(x1, y1, x2, y2, x3, y3)画三次贝塞尔曲线close()闭合 Contour。一个关键细节如果 Path 为空或最后一个 Verb 是kClose_Verb这些to类命令会先把 Last Point 隐式设为 (0, 0)再追加kMove_Verb与 (0, 0) 到数组中见 lineTo 的说明。这正是文档示例中conicTo 未由 moveTo 前置时新 Contour 起点接续上一条 Contour 终点的底层原因。3.3 Conic 权重的三种分支圆锥曲线的权重w直接决定了追加哪种 Verb见 conicTo 的文档注释w 有限且不等于 1追加kConic_Verb并记录权重到 conic weights 数组w 等于 1退化为二次贝塞尔追加kQuad_Verbw 非有限NaN/Inf退化为两条直线追加两次kLine_Verb。这解释了为什么头文件注释会警告conicTo()可能因权重不同而追加不同的 Verb因此依赖 Verb 数量的比较操作可能不匹配见 SkPath.h。3.4 用迭代器遍历 Path要读取 Path 的内容而非凭空构建Skia 提供两个迭代器SkPath::Iter可选项forceClose会把每个开放 Contour 视为闭合自动补kLine_VerbkClose_Verb并可通过 conicWeight() 读取当前 conic 权重SkPath::RawIter返回原始数据不做任何闭合处理。二者均通过next(SkPoint pts[4])按序返回 Verb并在数组耗尽时返回kDone_Verb。4. FillType决定填充区域与孔洞Path 包含一个 FillType填充类型它决定两件事重叠的 Contour 之间是形成填充fills还是孔洞holesLine 与 Curve 围成的区域其内部还是外部被填充。FillType 的四种取值在 SkPathTypes.h 中定义枚举值判定规则kWinding依据有符号边缘交叉次数的非零和判定内部默认值kEvenOdd依据边缘交叉次数的奇偶性判定内部kInverseWinding与 Winding 相同但绘制路径外部而非内部kInverseEvenOdd与 EvenOdd 相同但绘制路径外部而非内部其中非零环绕数与奇偶规则正是经典 SVG 中nonzero与evenodd填充规则的对应实现。官方示例fiddle36a995442c081ee779ecab2962d36e69演示了同一个 Path 分别以仅填充仅描边描边填充三种方式绘制的结果差异。4.1 在代码中操作 FillTypeSkPath.h 提供了完整的 FillType 操作接口getFillType()返回当前 FillTypesetFillType(ft)设置 FillType。头文件提示虽然不检查ft的合法性但超出 FillType 范围的值不受支持isInverseFillType()判断是否为kInverseWinding/kInverseEvenOddtoggleInverseFillType()切换为相反的反转类型内部实现为fFillType ^ 2因为反转位恰好是枚举值的 bit 1。4.2 一个典型应用五角星孔洞当两个 Contour 方向相反一个顺时针、一个逆时针且使用kWinding时重叠区域的非零环绕和会抵消从而形成孔洞而kEvenOdd则无论方向如何只要交叉次数为偶数即视为外部。选择哪个 FillType 会直接决定眼睛字母 O环形等图形能否正确镂空。5. 按值拷贝与 Copy-on-Write内容永不共享Path 的内容永远不会被共享按值拷贝 Path 会实际创建一个独立于原对象的新 Path但内部实现上拷贝不会立即复制内容而是等到某个对象被编辑时才真正复制从而减少内存占用、提升性能。这是典型的写时复制copy-on-write策略。从 SkPath.h 的成员定义 可以看到Path 持有一个sk_spSkPathRef fPathRef智能指针实际数据verb 数组、Point 数组、conic weights存放在共享的SkPathRef中而在 src/core/SkPath.cpp 中所有修改型操作都会先经过SkPathRef::Editor或检查fPathRef-unique()if (!fPathRef-unique()) { // 存在其他引用者先复制再修改 } SkPathRef::Editor ed(fPathRef); // 编辑时确保独占例如moveTo、lineTo等追加操作的实现见 src/core/SkPath.cpp 中大量SkPathRef::Editor的使用都会经由 Editor 在必要时触发底层复制。因此SkPath a; a.moveTo(0, 0); a.lineTo(100, 100); SkPath b a; // 共享底层数据开销极低 b.lineTo(0, 100); // b 首次被编辑此时才真正复制数据 // a 与 b 从此互不影响理解这一机制对性能敏感的场景如把 Path 传入函数、缓存几何、或在多个绘制层间复用很有帮助拷贝本身几乎免费编辑才会产生复制成本所以拷贝后各改各的是安全的惯用法。6. 综合示例与最佳实践6.1 绘制描边不填充的开放图形SkPath path; path.moveTo(20, 20); path.lineTo(120, 20); // 第一条 Line path.quadTo(120, 80, 70, 80); // 二次曲线 // 未调用 close()Contour 保持开放描边时端点使用 StrokeCap SkPaint paint; paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeWidth(4); paint.setStrokeCap(SkPaint::kRound_Cap); // 开放端点显示为圆点 canvas-drawPath(path, paint);6.2 用 FillType 控制孔洞SkPath path; // 外轮廓顺时针 path.addCircle(100, 100, 80); // 内孔逆时针 path.addCircle(100, 100, 40); path.setFillType(SkPathFillType::kWinding); // 方向相反 → 形成圆环孔洞 canvas-drawPath(path, paint);6.3 关键要点回顾Verb 决定语义kMove_Verb开启新 ContourkClose_Verb闭合当前 Contour二者共同划定轮廓边界点被共享相邻 Line/Curve 共用端点Point 序列天然紧凑闭合与描边样式挂钩闭合 Contour 在接缝处使用 StrokeJoin开放 Contour 在端点使用 StrokeCap零长度 Contour 在 Cap 可见时仍会绘制FillType 决定填充区域kWinding默认按环绕数、kEvenOdd按奇偶性判定内部反转类型绘制外部拷贝安全且廉价内容不共享但采用 copy-on-write编辑时才复制底层SkPathRef数据。官方文档还指出Path 支持序列化/反序列化serialize()/read()会写入 FillType、verb 数组、Point 数组与 conic weight见 SkPath.h并可通过 getSegmentMasks() 快速查询 Path 包含哪些类型的线段。这些能力与本文的 Contour、Verb、FillType 概念共同构成了 SkPath 的完整知识框架是进一步学习SkCanvas绘制与SkPaint样式的基础。赞分享图形学图像处理【免费下载链接】skiaSkia is a complete 2D graphic library for drawing Text, Geometries, and Images.项目地址https://gitcode.com/gh_mirrors/skia1/skia点击查看免费下载相关推荐pandas 2.0.0 升级指南从 Index 新 dtype 到 Copy-on-Write 的完整变更解析pandas 2.0.0 升级指南从 Index 新 dtype 到 Copy on Write 的完整变更解析 pandas 2.0.02023 年 4数据分析数据科学数据处理pandas 3.0 发布详解默认字符串 dtype、Copy-on-Write 与新的 pd.col 语法pandas 3.0 发布详解默认字符串 dtype、Copy on Write 与新的 pd.col 语法 导读 本文基于 pandas 官方博客的 pan数据分析数据科学数据处理Apache Hudi核心概念解析深入理解Copy-on-Write与Merge-on-ReadApache Hudi核心概念解析深入理解Copy on Write与Merge on Read Apache Hudi作为 开源分布式列存储系统 为大数据数据湖湖仓一体大数据数据存储上一篇当3D打印遇上Blender如何用3MF格式打破创意与制造之间的壁垒下一篇如何快速掌握NVIDIA Profile Inspector显卡性能终极优化指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?
咨询建站