本文主要是介绍2311skia,04绘制路径,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
分析Skia
绘画路径代码
绘画路径
尽管使用频率
相对绘画图像,绘画文本
低,但却是非常重要
的一个基本特性.所有不规则图形(椭圆,圆角矩形,三角形,简单的文字
),最后都要绘画路径
.
而且,若自己
实现一个2D
引擎,这块内容
是很有参考意义
的,用OpenGL
,都很少关注采样图像
了,对对
坐标就好.
但如何绘画菱角,圆弧,曲线
等仍是个难题
,这时就可参考Skia
中drawPath
的实现.
因为涉及较多
图形学知识,就不讲相关公式
了,只讲讲基本流程.
一,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
};
关于fFillType
中kWinding_FillType
和kEvenOdd_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
是绘画
所有不规则
形体的函数,用Bitmap
的Shader
,可制作不规则
形体图片.对凸集,和OpenGL
类似,Skia
也主要是切成三角片
后再渲染.
对凹集
,则是扫描行
了.渲染
和绘画图片
一样,构建Blitter
,调用Blitter
的blit
函数族渲染.
这篇关于2311skia,04绘制路径的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!