2311skia,04绘制路径

2023-11-27 03:45
文章标签 路径 绘制 04 2311skia

本文主要是介绍2311skia,04绘制路径,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

分析Skia绘画路径代码

绘画路径尽管使用频率相对绘画图像,绘画文本低,但却是非常重要的一个基本特性.所有不规则图形(椭圆,圆角矩形,三角形,简单的文字),最后都要绘画路径.
而且,若自己实现一个2D引擎,这块内容是很有参考意义的,用OpenGL,都很少关注采样图像了,对对坐标就好.
但如何绘画菱角,圆弧,曲线等仍是个难题,这时就可参考SkiadrawPath的实现.

因为涉及较多图形学知识,就不讲相关公式了,只讲讲基本流程.

一,SkPath

之前绘画图像时,并没有介绍SkBitmap,因为SkBitmap相对而言比较容易理解,网上文章也多.但这次的SkPath不同,研究它怎么用是需要一点精力的,因此在此先介绍它.

1,SkPath结构

去除成员函数后,看到SkPath包括如下几个成员,注释中补充了说明:

class SK_API SkPath {//`SkPath`中的主要内容,`SkAutoTUnref`是自解引用,之所以这么设计,是为了复制`SkPath`时,省去份量较多的复制点(只复制引用).由一系列线段组成SkAutoTUnref<SkPathRef> fPathRef;int fLastMoveToIndex;uint8_t fFillType;//如下四种类型之一//该枚举是注释了的.enum FillType {kWinding_FillType,//绘画所有线段包围成的区域kEvenOdd_FillType,//绘画被所有线段包围奇数次的区域kInverseWinding_FillType,//`kWinding_FillType`取反,即绘画不在该区域的点kInverseEvenOdd_FillType//取反第二个类型}mutable uint8_t     fConvexity;//凹凸性,临时计算mutable uint8_t     fDirection;//方向,顺时针/逆时针,临时计算
#ifdef SK_BUILD_FOR_ANDROIDconst SkPath*       fSourcePath;//`Hwui`中使用,暂不关注
#endif
};

关于fFillTypekWinding_FillTypekEvenOdd_FillType的区别,可看SkPath::contains.这是判断点是否在不规则几何体内的经典代码,很有参考意义.
SkPathRef内容如下:

class SkPathRef
{
private:mutable SkRect      fBounds;//边界,临时计算uint8_t             fSegmentMask;//表示该`Path`含有哪些种类的形状mutable uint8_t     fBoundsIsDirty;//缓存`fBounds`使用,表示是否需要重新计算`fBounds`mutable SkBool8     fIsFinite;    //边界有效,才有意义.mutable SkBool8     fIsOval;//`skia`不使用`stl`库而自带一套容器方案,可看下`SkPath::Iter`的实现SkPoint*            fPoints; //分配头针uint8_t*            fVerbs; //
//动作反向增长,刚过分配尾的点int                 fVerbCnt;int                 fPointCnt;size_t              fFreeSpace; //冗余但节省计算SkTDArray<SkScalar> fConicWeights;mutable uint32_t    fGenerationID;
};

2,SkPath的主要类型:

kMove_Verb:表示需要移动起点
kLine_Verb:直线
kQuad_Verb:二次曲线
kConic_Verb:圆锥曲线
kCubic_Verb:三次曲线
kClose_Verb:表闭合到某点
kDone_Verb:表结束

3,drawPath使用实例

#include "SkPath.h"
#include "SkCanvas.h"
#include "SkBitmap.h"
int main()
{SkBitmap dst;dst.allocN32Pixels(1000, 1000);SkCanvas c(dst);SkPath path;//*一个三角形path.moveTo(300,0);path.lineTo(400,100);path.lineTo(200,100);path.close();//*椭圆SkRect oval;oval.set(0, 0, 500, 600);path.addOval(oval);c.drawPath(path);return 1;
}

二,drawPath流程

1,基本流程,太多略.

2,填充算法说明
跟进最重要的sk_fill_path函数,如下为代码:

void sk_fill_path(const SkPath& path, const SkIRect* clipRect, SkBlitter* blitter,int start_y, int stop_y, int shiftEdgesUp,const SkRegion& clipRgn) {SkASSERT(&path && blitter);SkEdgeBuilder   builder;int count = builder.build(path, clipRect, shiftEdgesUp);SkEdge**    list = builder.edgeList();if (count < 2) {if (path.isInverseFillType()) {//因为反向填充状态,因此的调用者已在顶部`(start_y)`上方绘画了,并将在底部下方`(stop_y)`绘画.因此,要限制在剪辑和这两个限制的交点上`绘图`.SkIRect rect = clipRgn.getBounds();if (rect.fTop < start_y) {rect.fTop = start_y;}if (rect.fBottom > stop_y) {rect.fBottom = stop_y;}if (!rect.isEmpty()) {blitter->blitRect(rect.fLeft << shiftEdgesUp,rect.fTop << shiftEdgesUp,rect.width() << shiftEdgesUp,rect.height() << shiftEdgesUp);}}return;}SkEdge headEdge, tailEdge, *last;//在排序到`双链`列表后,返回第一条边和最后一条边SkEdge* edge = sort_edges(list, count, &last);headEdge.fPrev = NULL;headEdge.fNext = edge;headEdge.fFirstY = kEDGE_HEAD_Y;headEdge.fX = SK_MinS32;edge->fPrev = &headEdge;tailEdge.fPrev = last;tailEdge.fNext = NULL;tailEdge.fFirstY = kEDGE_TAIL_Y;last->fNext = &tailEdge;//现在`edge`是排序链接列表头start_y <<= shiftEdgesUp;stop_y <<= shiftEdgesUp;if (clipRect && start_y < clipRect->fTop) {start_y = clipRect->fTop;}if (clipRect && stop_y > clipRect->fBottom) {stop_y = clipRect->fBottom;}InverseBlitter  ib;PrePostProc     proc = NULL;if (path.isInverseFillType()) {ib.setBlitter(blitter, clipRgn.getBounds(), shiftEdgesUp);blitter = &ib;proc = PrePostInverseBlitterProc;}if (path.isConvex() && (NULL == proc)) {walk_convex_edges(&headEdge, path.getFillType(), blitter, start_y, stop_y, NULL);} else {walk_edges(&headEdge, path.getFillType(), blitter, start_y, stop_y, proc);}
}

不考虑Inverse,主要就是两步:
(1)生成一系列边:SkEdge
(2)遍历渲染各边所围出来的区域.

凸集渲染比较简单,因为可保证,一定会渲染任意两条边+闭合线所围成区域:
(1)取初始的左右两条边.
(2)渲染左右边+闭合边所围成的区域(一般为三角,两边平行取矩形)
(3)迭代刷新左右两边(如果是曲线需要多次刷新)

static void walk_convex_edges(SkEdge* prevHead, SkPath::FillType, SkBlitter* blitter, int start_y, int stop_y, PrePostProc proc) {validate_sort(prevHead->fNext);SkEdge* leftE = prevHead->fNext;SkEdge* riteE = leftE->fNext;SkEdge* currE = riteE->fNext;
#if 0int local_top = leftE->fFirstY;SkASSERT(local_top == riteE->fFirstY);
#else//曲线的`边角`斩波器可能会导致`初始`边角不对齐,因此取最大值.int local_top = SkMax32(leftE->fFirstY, riteE->fFirstY);
#endifSkASSERT(local_top >= start_y);for (;;) {SkASSERT(leftE->fFirstY <= stop_y);SkASSERT(riteE->fFirstY <= stop_y);if (leftE->fX > riteE->fX || (leftE->fX == riteE->fX && leftE->fDX > riteE->fDX)) {SkTSwap(leftE, riteE);}int local_bot = SkMin32(leftE->fLastY, riteE->fLastY);local_bot = SkMin32(local_bot, stop_y - 1);SkASSERT(local_top <= local_bot);SkFixed left = leftE->fX;SkFixed dLeft = leftE->fDX;SkFixed rite = riteE->fX;SkFixed dRite = riteE->fDX;int count = local_bot - local_top;SkASSERT(count >= 0);if (0 == (dLeft | dRite)) {int L = SkFixedRoundToInt(left);int R = SkFixedRoundToInt(rite);if (L < R) {count += 1;blitter->blitRect(L, local_top, R - L, count);left += count * dLeft;rite += count * dRite;}local_top = local_bot + 1;} else {do {int L = SkFixedRoundToInt(left);int R = SkFixedRoundToInt(rite);if (L < R) {blitter->blitH(L, local_top, R - L);}left += dLeft;rite += dRite;local_top += 1;} while (--count >= 0);}leftE->fX = left;riteE->fX = rite;if (update_edge(leftE, local_bot)) {if (currE->fFirstY >= stop_y) {break;}leftE = currE;currE = currE->fNext;}if (update_edge(riteE, local_bot)) {if (currE->fFirstY >= stop_y) {break;}riteE = currE;currE = currE->fNext;}SkASSERT(leftE);SkASSERT(riteE);//查看的底部剪切SkASSERT(local_top == local_bot + 1);if (local_top >= stop_y) {break;}}
}

凹集或判断不了凹凸性的,就比较复杂,需要一条线一条线去渲染,每次渲染还得判断奇偶性:
代码如下,就不分析了:

static void walk_edges(SkEdge* prevHead, SkPath::FillType fillType, SkBlitter* blitter, int start_y, int stop_y, PrePostProc proc) {validate_sort(prevHead->fNext);int curr_y = start_y;//无论是否逆向,奇偶返回1,展开返回`-1`int windingMask = (fillType & 1) ? 1 : -1;for (;;) {int     w = 0;int     left SK_INIT_TO_AVOID_WARNING;bool    in_interval = false;SkEdge* currE = prevHead->fNext;SkFixed prevX = prevHead->fX;validate_edges_for_y(currE, curr_y);if (proc) {proc(blitter, curr_y, PREPOST_START);    //预处理}while (currE->fFirstY <= curr_y) {SkASSERT(currE->fLastY >= curr_y);int x = SkFixedRoundToInt(currE->fX);w += currE->fWinding;if ((w & windingMask) == 0) { //完成了中场休息SkASSERT(in_interval);int width = x - left;SkASSERT(width >= 0);if (width)blitter->blitH(left, curr_y, width);in_interval = false;} else if (!in_interval) {left = x;in_interval = true;}SkEdge* next = currE->fNext;SkFixed newX;if (currE->fLastY == curr_y) {    //完成了该边角吗if (currE->fCurveCount < 0) {if (((SkCubicEdge*)currE)->updateCubic()) {SkASSERT(currE->fFirstY == curr_y + 1);newX = currE->fX;goto NEXT_X;}} else if (currE->fCurveCount > 0) {if (((SkQuadraticEdge*)currE)->updateQuadratic()) {newX = currE->fX;goto NEXT_X;}}remove_edge(currE);} else {SkASSERT(currE->fLastY > curr_y);newX = currE->fX + currE->fDX;currE->fX = newX;NEXT_X:if (newX < prevX) { //直到排序x,向后纹波`currE`backward_insert_edge_based_on_x(currE  SkPARAM(curr_y));} else {prevX = newX;}}currE = next;SkASSERT(currE);}if (proc) {proc(blitter, curr_y, PREPOST_END);    //后处理}curr_y += 1;if (curr_y >= stop_y) {break;}//现在`currE`指向`Yint`大于`curr_y`的第一条边insert_new_edges(currE, curr_y);}
}

3,扫描行流程
较简单,就不介绍了.

三,总结

drawPath绘画所有不规则形体的函数,用BitmapShader,可制作不规则形体图片.对凸集,和OpenGL类似,Skia也主要是切成三角片后再渲染.
凹集,则是扫描行了.渲染绘画图片一样,构建Blitter,调用Blitterblit函数族渲染.

这篇关于2311skia,04绘制路径的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/426994

相关文章

使用Python绘制3D堆叠条形图全解析

《使用Python绘制3D堆叠条形图全解析》在数据可视化的工具箱里,3D图表总能带来眼前一亮的效果,本文就来和大家聊聊如何使用Python实现绘制3D堆叠条形图,感兴趣的小伙伴可以了解下... 目录为什么选择 3D 堆叠条形图代码实现:从数据到 3D 世界的搭建核心代码逐行解析细节优化应用场景:3D 堆叠图

SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志

《SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志》在SpringBoot项目中,使用logback-spring.xml配置屏蔽特定路径的日志有两种常用方式,文中的... 目录方案一:基础配置(直接关闭目标路径日志)方案二:结合 Spring Profile 按环境屏蔽关

VSCode设置python SDK路径的实现步骤

《VSCode设置pythonSDK路径的实现步骤》本文主要介绍了VSCode设置pythonSDK路径的实现步骤,包括命令面板切换、settings.json配置、环境变量及虚拟环境处理,具有一定... 目录一、通过命令面板快速切换(推荐方法)二、通过 settings.json 配置(项目级/全局)三、

使用Python和Matplotlib实现可视化字体轮廓(从路径数据到矢量图形)

《使用Python和Matplotlib实现可视化字体轮廓(从路径数据到矢量图形)》字体设计和矢量图形处理是编程中一个有趣且实用的领域,通过Python的matplotlib库,我们可以轻松将字体轮廓... 目录背景知识字体轮廓的表示实现步骤1. 安装依赖库2. 准备数据3. 解析路径指令4. 绘制图形关键

如何更改pycharm缓存路径和虚拟内存分页文件位置(c盘爆红)

《如何更改pycharm缓存路径和虚拟内存分页文件位置(c盘爆红)》:本文主要介绍如何更改pycharm缓存路径和虚拟内存分页文件位置(c盘爆红)问题,具有很好的参考价值,希望对大家有所帮助,如有... 目录先在你打算存放的地方建四个文件夹更改这四个路径就可以修改默认虚拟内存分页js文件的位置接下来从高级-

QT6中绘制UI的两种方法详解与示例代码

《QT6中绘制UI的两种方法详解与示例代码》Qt6提供了两种主要的UI绘制技术:​​QML(QtMeta-ObjectLanguage)​​和​​C++Widgets​​,这两种技术各有优势,适用于不... 目录一、QML 技术详解1.1 QML 简介1.2 QML 的核心概念1.3 QML 示例:简单按钮

一文详解如何查看本地MySQL的安装路径

《一文详解如何查看本地MySQL的安装路径》本地安装MySQL对于初学者或者开发人员来说是一项基础技能,但在安装过程中可能会遇到各种问题,:本文主要介绍如何查看本地MySQL安装路径的相关资料,需... 目录1. 如何查看本地mysql的安装路径1.1. 方法1:通过查询本地服务1.2. 方法2:通过MyS

Python如何调用指定路径的模块

《Python如何调用指定路径的模块》要在Python中调用指定路径的模块,可以使用sys.path.append,importlib.util.spec_from_file_location和exe... 目录一、sys.path.append() 方法1. 方法简介2. 使用示例3. 注意事项二、imp

Python使用Matplotlib绘制3D曲面图详解

《Python使用Matplotlib绘制3D曲面图详解》:本文主要介绍Python使用Matplotlib绘制3D曲面图,在Python中,使用Matplotlib库绘制3D曲面图可以通过mpl... 目录准备工作绘制简单的 3D 曲面图绘制 3D 曲面图添加线框和透明度控制图形视角Matplotlib

使用Python实现矢量路径的压缩、解压与可视化

《使用Python实现矢量路径的压缩、解压与可视化》在图形设计和Web开发中,矢量路径数据的高效存储与传输至关重要,本文将通过一个Python示例,展示如何将复杂的矢量路径命令序列压缩为JSON格式,... 目录引言核心功能概述1. 路径命令解析2. 路径数据压缩3. 路径数据解压4. 可视化代码实现详解1