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方法画线。