skia draw path flow

1.draw path general flow



由上图,drawOval,drawArc,drawRoundRect等基本图形的绘制,或者不规则图形的绘制都会调用drawPath。与draw bitmap flow类似,drawPath的实际实现也是在SkDraw中,这部分的flow如下:

2.绘制三角形轮廓

{
SkPaint paint;
paint.setAntiAlias(false); //无抗锯齿
paint.setStyle(SkPaint::kStroke_Style); //style 为轮廓
SkPath path;
/*一个三角形*/
path.moveTo(300,0); //创建点(300,0),并记录verb为kMove_Verb
path.lineTo(400,100); //创建点(400,100),并记录verb为kLine_Verb
path.lineTo(200,100); //创建点(200,100),并记录verb为kLine_Verb
path.close(); //闭合path,记录verb为kClose_Verb
canvas->drawPath(path,paint);

}

代码中设置的条件如下:

No AntiAlias

No path effect

kStroke_Style & stroke width == 0

Dst bitmap color type = 32

Paint color = black (default)

No shader

同时代码中创建了一个path,并使用path的成员函数创建了三个点和四个动作(verb),这些点和verb最终会绘制为一个三角形。SkPath是通过创建点和对应的verb来绘制路径的,它支持的verb有以下这些:

enum Verb {
kMove_Verb, //!< iter.next returns 1 point 表示需要移动起点
kLine_Verb, //!< iter.next returns 2 points 直线
kQuad_Verb, //!< iter.next returns 3 points 二次曲线
kConic_Verb, //!< iter.next returns 3 points + iter.conicWeight() 圆锥曲线
kCubic_Verb, //!< iter.next returns 4 points 三次曲线
kClose_Verb, //!< iter.next returns 1 point (contour's moveTo pt) 表闭合到某点
kDone_Verb, //!< iter.next returns 0 points 表结束

};

创建完path后进入canvas的draw api,这部分与drawBitmap流程基本类似,但在判断剪裁区域与path边界是否相交时有些区别:

if (!path.isInverseFillType() && paint.canComputeFastBounds()) {
const SkRect& pathBounds = path.getBounds();
bounds = &paint.computeFastBounds(pathBounds, &storage);
if (this->quickReject(*bounds)) {
return;
}
}

这里首先判断了path的填充类型,关于path的填充类型有以下几种:

/*enum FillType {
kWinding_FillType,//绘制所有线段包围成的区域
kEvenOdd_FillType,//绘制被所有线段包围奇数次的区域)
kInverseWinding_FillType,//kWinding_FillType取反,即绘制不在该区域的点
kInverseEvenOdd_FillType//第二种type取反
}*/

我们的代码中并没有设置path填充类型,因此采用默认类型为kWinding_FillType。

经过两层循环之后进入SkDraw的drawPath部分。根据第一节draw path general flow,可以把这部分每个分支作为一个小节来讲。

2.1 treat as hair line

这部分flow为:

先判断SkDrawTreatAsHairline函数返回值,函数实现为:

inline bool SkDrawTreatAsHairline(const SkPaint& paint, const SkMatrix& matrix,
SkScalar* coverage) {
if (SkPaint::kStroke_Style != paint.getStyle()) {
return false;
}

SkScalar strokeWidth = paint.getStrokeWidth();
if (0 == strokeWidth) {
*coverage = SK_Scalar1;
return true;
}

if (!paint.isAntiAlias()) {
return false;
}

return SkDrawTreatAAStrokeAsHairline(strokeWidth, matrix, coverage);
}

因为我们没有设置strokeWidth,因此默认为0,coverage ==1. 然后我们也没有设置xfermode,因此后面的xfermodeSupportsCoverageAsAlpha也不需要判断了。

2.2 paint->getPathEffect() || paint->getStyle() != SkPaint::kFill_Style

这部分代码为:

if (paint->getPathEffect() || paint->getStyle() != SkPaint::kFill_Style) {
SkRect cullRect;
const SkRect* cullRectPtr = NULL;
if (this->computeConservativeLocalClipBounds(&cullRect)) {
cullRectPtr = &cullRect;
}
doFill = paint->getFillPath(*pathPtr, &tmpPath, cullRectPtr);
pathPtr = &tmpPath;
}

我们没有设置path effect,但是style不是kFill_Style,因此这部分需要执行。首先要进行computeConservativeLocalClipBounds,因为path边界很有可能正好在clip的边界,因此这里需要使clip的边界向外扩1行1列,为path边界留出一个hair line。

然后,如果设置了path effect,则需要getFillPath中进行相关动作,但我们没有设置,因此这里doFill标记为false,不需要填充。

2.3 paint set Rasterizer?

没设置,跳过。

2.4Transform src path to dst path by matrix

根据canvas当前的矩阵状态,把源path进行坐标变换,变换后的结果就是dst path。

2.5 Chooose blitter

这里跟drawBitmap的流程一致,都是使用SkBlitter::Choose去选择blitter。依据条件:

Dst bitmap color type = 32

Paint color = black (default)

我们使用的blitter为SkARGB32_Black_Blitter。并且在它的构造函数中需要去选择SkBlitRow::ColorProcFactory()和SkBlitRow::ColorRectProcFactory()。这两个函数同样也是平台相关的。

2.6 paint set mask filter?

没设置,跳过。

2.7 填充 or 绘制轮廓

在2.2中判断出doFill为false,并且不考虑抗锯齿,因此调用SkScan::HairPath()去绘制轮廓。

在SkScan::HairPath()->HairPath()使用了SkPath::Iter迭代器,构造迭代器时会把path中保存的点和verb的首地址告诉迭代器。iter使用next函数根据verb去取相应动作的点。随后在一个while循环中,

根据next返回的verb和点,去做相应的处理。这部分代码如下:

SkPath::Iter iter(path, false);
SkPoint pts[4];
SkPath::Verb verb;
SkAutoConicToQuads converter;

while ((verb = iter.next(pts, false)) != SkPath::kDone_Verb) {
switch (verb) {
case SkPath::kMove_Verb:
break;
case SkPath::kLine_Verb:
lineproc(pts[0], pts[1], clip, blitter);
break;
case SkPath::kQuad_Verb:
hairquad(pts, clip, blitter, compute_quad_level(pts), lineproc);
break;
case SkPath::kConic_Verb: {
// how close should the quads be to the original conic?
const SkScalar tol = SK_Scalar1 / 4;
const SkPoint* quadPts = converter.computeQuads(pts,
iter.conicWeight(), tol);
for (int i = 0; i < converter.countQuads(); ++i) {
int level = compute_quad_level(quadPts);
hairquad(quadPts, clip, blitter, level, lineproc);
quadPts += 2;
}
break;
}
case SkPath::kCubic_Verb:
haircubic(pts, clip, blitter, kMaxCubicSubdivideLevel, lineproc);
break;
case SkPath::kClose_Verb:
break;
case SkPath::kDone_Verb:
break;
}
}

我们的代码中主要是画直线,因此会去走这个分支:lineproc(pts[0], pts[1], clip, blitter);
这里lineproc为SkScan::HairLineRgn。这个函数主要根据已知的两点,和直线的斜率使用DDA方法画线。

示例结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值