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函数族渲染.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/171050.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

cjson库打包数据实现方法

使用 cJson 库&#xff0c;在C语言环境下&#xff0c;打包一个cJson字符串&#xff1a; int CreateArryJsonString(void) {cJSON *cJsonArr cJSON_CreateArray();cJSON *sJsonObj1 cJSON_CreateObject();cJSON_AddStringToObject(sJsonObj1, "test1", "test1…

动态规划学习——子序列问题

目录 ​编辑 一&#xff0c;最长定差子序列 1.题目 2&#xff0c;题目接口 3&#xff0c;解题思路及其代码 一&#xff0c;最长定差子序列 1.题目 给你一个整数数组 arr 和一个整数 difference&#xff0c;请你找出并返回 arr 中最长等差子序列的长度&#xff0c;该子序列…

机器学习【03】在本地浏览器使用远程服务器的Jupyter Notebook【conda环境】

1.激活虚拟环境 conda activate 虚拟环境名字2.虚拟环境下安装jupyter notebook pip install jupyter3.配置 jupyter 文件 在 Jupyter Notebook 的配置目录中生成一个配置文件 jupyter_notebook_config.py jupyter notebook --generate-config3.设置密码 jupyter notebook …

C/C++ 常用加密与解密算法

计算机安全和数据隐私是现代应用程序设计中至关重要的方面。为了确保数据的机密性和完整性&#xff0c;常常需要使用加密和解密算法。C是一种广泛使用的编程语言&#xff0c;提供了许多加密和解密算法的实现。本文将介绍一些在C中常用的加密与解密算法&#xff0c;这其中包括Xo…

[vxe-table] vxe-table-column配合v-if导致列样式与位置错乱

<vxe-table-column v-if"pageInfo.id 4 ||pageInfo.id 8" title"上报类型" width"100" key1><template v-slot"{row}"><span>咨询工具</span></template> </vxe-table-column>//或者<vxe-ta…

操作NAND flash W25N01G

文章目录 W25N01G1 描述2 特点3 封装3.3.2 连接线 4 引脚/CSDO/WP/Hold SPI指令标准SPI命令双SPI四元SPI命令写保护 5 地址PA与PC最后一个扇区 OTP寄存器1块保护清除块保护指令* WP-E 寄存器2寄存器3BUSYP-FAILE-FAILECC位 8 命令8.1 装置ID 指令解读写状态寄存器 注意内容上拉…

Java,File类与IO流,处理流:缓冲流、转换流、数据流、对象流

目录 处理流之一&#xff1a;缓冲流 四种缓冲流&#xff1a; 缓冲流的作用&#xff1a; 使用的方法&#xff1a; 处理文本文件的字符流&#xff1a; 处理非文本文件的字节流&#xff1a; 操作步骤&#xff1a; 处理流之二&#xff1a;转换流 转换流的使用&#xff1a; …

企业编码生成程序Python毕业设计

&#xff08;1&#xff09;生成6位数字防伪编码。当用户在主程序界面中输入数字“1”菜单项时&#xff0c;将进入“生成6位数字防伪编码 &#xff08;213563型&#xff09;”的功能执行任务。此时要求输入生成防伪码的数量&#xff0c;可以根据需要输入生成防伪码的数量。按下&…

范围查询 range级别 继续优化思路

问题&#xff1a; 这几天工作遇到了一个问题。千万级别的表&#xff0c;每秒钟产生很多数据&#xff0c;select count(id) from table where flag 1 and create_time < 2023.11.07;分区表&#xff0c;range级别&#xff0c;已经是走create_time列上的索引&#xff0c;flag…

springboot宠物店管理系统-计算机毕设 附源码 32041

SpringBoot宠物店管理系统 摘 要 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;宠物行业当然也不例外。宠物店管理系统是以实际运用为开发背景&#xff0c;运用软件工程原理…

多级嵌套vue同步调用用法

//需求 要求同步调用initGame2方法 //调用方法 this.initSocket(); //定义方法为同步 async initSocket() { //调用为同步 await this.initGame2(); //定义方法为同步 async initGame2() {const e await w({ url: //定义w方法 const w e.create({ baseURL: http://my_url:8…

python pdf转txt文本、pdf转json

文章目录 一、前言二、实现方法1. 目录结构2. 代码 一、前言 此方法只能转文本格式的pdf&#xff0c;如果是图片格式的pdf需要用到ocr包&#xff0c;以后如果有这方面需求再加这个方法 二、实现方法 1. 目录结构 2. 代码 pdf2txt.py 代码如下 #!/usr/bin/env python # -*- …

H5ke12--3--iframe--编辑邮箱的制作

下面我们来window.iframes[] frames是一个全局变量&#xff0c;它是一个对象数组&#xff0c;其中包含当前窗口中的所有框架&#xff08;如果存在&#xff09;。 在这段代码中&#xff0c;let frameframes[0];是将第一个框架赋值给变量frame。通过frame.document.designMode&q…

【ArcGIS Pro微课1000例】0037:ArcGIS Pro中模型构建器的使用---以shp批量转kml/kmz为例

文章目录 一、ArcGIS Pro模型构建器介绍二、shp批量转kml/kmz1. 打开模型构建器2. 添加工作空间4. 添加【创建要素图层】工具5. 添加【图层转kml】工具6. 输出文件命名7. 运行模型三、模型另存为1.py文件2. 保存为工具一、ArcGIS Pro模型构建器介绍 模型构建器是一种可视化编程…

感冒的六大经方2

3 葛根汤 处方内容是&#xff1a;葛根15克&#xff0c;麻黄5克&#xff0c;桂枝10克&#xff0c;白芍10克&#xff0c;生薑二片&#xff0c; 炙甘草10克&#xff0c;大枣十枚 每付药加入六碗水使用大火来煮成二碗&#xff0c;成人于每三小时空腹时喝一碗&#xff0c;小孩减半…

C语言从入门到精通之【表达式和语句】

1 表达式 表达式由运算符和运算对象组成&#xff0c;最简单的表达式一个单独的运算对象。每个表达式都有一个值&#xff0c;并且是根据运算符优先级规定的顺序来执行&#xff0c;以下是一些表达式&#xff1a; 4 -6 421 a*(b c/d)/20 q 5*2 x q % 3 #q > 3 2 语句 语句…

yolov5从英伟达平台移植到华为昇腾开发板上的思路

作者&#xff1a;朱金灿 来源&#xff1a;clever101的专栏 为什么大多数人学不会人工智能编程&#xff1f;>>> 最近需要将yolov5代码从英伟达平台移植到华为昇腾开发板上。搜了一些代码和资料&#xff0c;大致明白了二者的差别。 1.二者使用的模型文件不一样 yolov…

【unity实战】实现一个放置3d物品建造装修系统(附项目源码)

文章目录 最终效果前言绘制开始场景素材开始放置旋转物体扩展优化1. 绘制地图边界&#xff0c;确保放置物品在指定区域内工作2. 让模型所占面积大小更加准确3. 隐藏白色瓦片指示区域 最终效果其他源码参考完结 最终效果 前言 其实3d物品建造装修系统之前就已经做过了&#xff…

2024年天津天狮学院食品质量与安全专业《普通化学》考试大纲

2024年天津天狮学院食品质量与安全专业高职升本入学考试《普通化学》考试大纲 一、考试性质 《普通化学》专业课程考试是天津天狮学院食品质量与安全专业高职升本入学考试 的必考科目之一&#xff0c;其性质是考核学生是否达到了升入本科继续学习的要求而进行的选拔性考试。《…