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

相关文章

hdu2544(单源最短路径)

模板题: //题意:求1到n的最短路径,模板题#include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<queue>#include<set>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#i

poj 1734 (floyd求最小环并打印路径)

题意: 求图中的一个最小环,并打印路径。 解析: ans 保存最小环长度。 一直wa,最后终于找到原因,inf开太大爆掉了。。。 虽然0x3f3f3f3f用memset好用,但是还是有局限性。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#incl

【WebGPU Unleashed】1.1 绘制三角形

一部2024新的WebGPU教程,作者Shi Yan。内容很好,翻译过来与大家共享,内容上会有改动,加上自己的理解。更多精彩内容尽在 dt.sim3d.cn ,关注公众号【sky的数孪技术】,技术交流、源码下载请添加微信号:digital_twin123 在 3D 渲染领域,三角形是最基本的绘制元素。在这里,我们将学习如何绘制单个三角形。接下来我们将制作一个简单的着色器来定义三角形内的像素

Flutter 进阶:绘制加载动画

绘制加载动画:由小圆组成的大圆 1. 定义 LoadingScreen 类2. 实现 _LoadingScreenState 类3. 定义 LoadingPainter 类4. 总结 实现加载动画 我们需要定义两个类:LoadingScreen 和 LoadingPainter。LoadingScreen 负责控制动画的状态,而 LoadingPainter 则负责绘制动画。

取得 Git 仓库 —— Git 学习笔记 04

取得 Git 仓库 —— Git 学习笔记 04 我认为, Git 的学习分为两大块:一是工作区、索引、本地版本库之间的交互;二是本地版本库和远程版本库之间的交互。第一块是基础,第二块是难点。 下面,我们就围绕着第一部分内容来学习,先不考虑远程仓库,只考虑本地仓库。 怎样取得项目的 Git 仓库? 有两种取得 Git 项目仓库的方法。第一种是在本地创建一个新的仓库,第二种是把其他地方的某个

【408DS算法题】039进阶-判断图中路径是否存在

Index 题目分析实现总结 题目 对于给定的图G,设计函数实现判断G中是否含有从start结点到stop结点的路径。 分析实现 对于图的路径的存在性判断,有两种做法:(本文的实现均基于邻接矩阵存储方式的图) 1.图的BFS BFS的思路相对比较直观——从起始结点出发进行层次遍历,遍历过程中遇到结点i就表示存在路径start->i,故只需判断每个结点i是否就是stop

Android Environment 获取的路径问题

1. 以获取 /System 路径为例 /*** Return root of the "system" partition holding the core Android OS.* Always present and mounted read-only.*/public static @NonNull File getRootDirectory() {return DIR_ANDR

利用matlab bar函数绘制较为复杂的柱状图,并在图中进行适当标注

示例代码和结果如下:小疑问:如何自动选择合适的坐标位置对柱状图的数值大小进行标注?😂 clear; close all;x = 1:3;aa=[28.6321521955954 26.2453660695847 21.69102348512086.93747104431360 6.25442246899816 3.342835958564245.51365061796319 4.87

图的最短路径算法——《啊哈!算法》

图的实现方式 邻接矩阵法 int[][] map;// 图的邻接矩阵存储法map = new int[5][5];map[0] = new int[] {0, 1, 2, 3, 4};map[1] = new int[] {1, 0, 2, 6, 4};map[2] = new int[] {2, 999, 0, 3, 999};map[3] = new int[] {3, 7

vcpkg子包路径批量获取

获取vcpkg 子包的路径,并拼接为set(CMAKE_PREFIX_PATH “拼接路径” ) import osdef find_directories_with_subdirs(root_dir):# 构建根目录下的 "packages" 文件夹路径root_packages_dir = os.path.join(root_dir, "packages")# 如果 "packages"