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//第二种type取反
        }*/
    mutable uint8_t     fConvexity;//凹凸性,临时计算
    mutable uint8_t     fDirection;//方向,顺时针/逆时针,临时计算
#ifdef SK_BUILD_FOR_ANDROID
    const 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;    // only meaningful if bounds are valid
    mutable SkBool8     fIsOval;
    /*skia不使用stl库而采用的一套容器方案,具体不细说,可看下 SkPath::Iter 的实现*/
    SkPoint*            fPoints; // points to begining of the allocation
    uint8_t*            fVerbs; // points just past the end of the allocation (verbs grow backwards)
    int                 fVerbCnt;
    int                 fPointCnt;
    size_t              fFreeSpace; // redundant but saves computation
    SkTDArray<SkScalar> fConicWeights;
    mutable uint32_t    fGenerationID;
};

2、SkPath的主要类型:

kMove_Verb:表示需要移动起点
kLine_Verb:直线
kQuad_Verb:二次曲线
kConic_Verb:圆锥曲线
kCubic_Verb:三次曲线
kClose_Verb:表闭合到某点
kDone_Verb:表结束

#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流程


填充算法说明
我们跟进最重要的函数 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()) {
            /*
             *  Since we are in inverse-fill, our caller has already drawn above
             *  our top (start_y) and will draw below our bottom (stop_y). Thus
             *  we need to restrict our drawing to the intersection of the clip
             *  and those two limits.
             */
            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;
    // this returns the first and last edge after they're sorted into a dlink list
    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;
    // now edge is the head of the sorted linklist
    start_y <<= shiftEdgesUp;
    stop_y <<= shiftEdgesUp;
    if (clipRect && start_y < clipRect->fTop) {
        start_y = clipRect->fTop;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值