skia DrawLooper

本次學習drawLooper.cpp中有關SkDrawLooper類的用法,並且分析了canvas draw api中的二層循環的作用。

SkDrawLooper有兩個子類:SkLayerDrawLooper和SkBlurDrawLooper。

先看一下drawLooper.cpp裏面的例子,主要看onDraw()做什麼:

virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE {
        this->init();//初始化

        SkPaint  paint;
        paint.setAntiAlias(true);//設置抗鋸齒
        paint.setTextSize(SkIntToScalar(72));//文字大小
        paint.setLooper(fLooper);//設置SkDrawLooper

        canvas->drawCircle(SkIntToScalar(50), SkIntToScalar(50),
                           SkIntToScalar(30), paint);//畫圓
        canvas->drawRectCoords(SkIntToScalar(150), SkIntToScalar(50),
                               SkIntToScalar(200), SkIntToScalar(100), paint);//畫矩形

        canvas->drawText("Looper", 6, SkIntToScalar(230), SkIntToScalar(100),
                         paint);//畫文字
}

在onDraw()可以看到,這個函數在固定位置繪製了一個圓、一個矩形和一個“looper”文字。而在最終跑出的結果中可以看到,繪製的這三個圖形都有一個模糊陰影,並且三個圖形的邊緣爲紅白相間,中間爲藍色填充。


造成這樣結果的始作俑者是init()函數:

    void init() {
        if (fLooper) return;

        static const struct {              //匿名結構體定義了一組描述參數
            SkColor         fColor;        //顏色
            SkPaint::Style  fStyle;        //path style
            SkScalar        fWidth;        //線寬
            SkScalar        fOffset;       //blur偏移
            SkScalar        fBlur;         //blur sigma輸入參數
        } gParams[] = {                    //gParams定義了4組不同效果                                                                 
            { SK_ColorWHITE, SkPaint::kStroke_Style, SkIntToScalar(1)*3/4, 0, 0 },	
            { SK_ColorRED, SkPaint::kStroke_Style, SkIntToScalar(4), 0, 0 },
            { SK_ColorBLUE, SkPaint::kFill_Style, 0, 0, 0 },
            { 0x88000000, SkPaint::kFill_Style, 0, SkIntToScalar(10), SkIntToScalar(3) }
        };

        SkLayerDrawLooper::Builder looperBuilder;//SkLayerDrawLooper的內部類

        SkLayerDrawLooper::LayerInfo info;
        info.fPaintBits = SkLayerDrawLooper::kStyle_Bit | SkLayerDrawLooper::kMaskFilter_Bit;
        info.fColorMode = SkXfermode::kSrc_Mode;

        for (size_t i = 0; i < SK_ARRAY_COUNT(gParams); i++) {
            info.fOffset.set(gParams[i].fOffset, gParams[i].fOffset);
            SkPaint* paint = looperBuilder.addLayer(info);
            paint->setColor(gParams[i].fColor);
            paint->setStyle(gParams[i].fStyle);
            paint->setStrokeWidth(gParams[i].fWidth);
            if (gParams[i].fBlur > 0) {
                SkMaskFilter* mf = SkBlurMaskFilter::Create(kNormal_SkBlurStyle,
                                         SkBlurMask::ConvertRadiusToSigma(gParams[i].fBlur));
                paint->setMaskFilter(mf)->unref();
            }
        }
        fLooper = looperBuilder.detachLooper();
    }
看一下init()函數中的兩個類:SkLayerDrawLooper::LayerInfo和SkLayerDrawLooper::Builder。

對於SkLayerDrawLooper::LayerInfo,skia的描述如下:

/**
     *  Info for how to apply the layer's paint and offset.
     *
     *  fColorMode controls how we compute the final color for the layer:
     *      The layer's paint's color is treated as the SRC
     *      The draw's paint's color is treated as the DST
     *      final-color = Mode(layers-color, draws-color);
     *  Any SkXfermode::Mode will work. Two common choices are:
     *      kSrc_Mode: to use the layer's color, ignoring the draw's
     *      kDst_Mode: to just keep the draw's color, ignoring the layer's
     */
    struct SK_API LayerInfo {
        BitFlags            fPaintBits;
        SkXfermode::Mode    fColorMode;
        SkVector            fOffset;
        bool                fPostTranslate; //!< applies to fOffset

        /**
         *  Initial the LayerInfo. Defaults to settings that will draw the
         *  layer with no changes: e.g.
         *      fPaintBits == 0
         *      fColorMode == kDst_Mode
         *      fOffset == (0, 0)
         */
        LayerInfo();
    };
init()函數中定義了info的fPaintBits、fColorMode和fOffset。

再來看SkLayerDrawLooper::Builder:

    class SK_API Builder {
    public:
        Builder();
        ~Builder();

        /**
         *  Call for each layer you want to add (from top to bottom).
         *  This returns a paint you can modify, but that ptr is only valid until
         *  the next call made to addLayer().
         */
        SkPaint* addLayer(const LayerInfo&);

        /**
         *  This layer will draw with the original paint, at the specified offset
         */
        void addLayer(SkScalar dx, SkScalar dy);

        /**
         *  This layer will with the original paint and no offset.
         */
        void addLayer() { this->addLayer(0, 0); }

        /// Similar to addLayer, but adds a layer to the top.
        SkPaint* addLayerOnTop(const LayerInfo&);

        /**
          * Pass list of layers on to newly built looper and return it. This will
          * also reset the builder, so it can be used to build another looper.
          */
        SkLayerDrawLooper* detachLooper();

    private:
        Rec* fRecs;
        Rec* fTopRec;
        int  fCount;
    };
在init()函數中,SkLayerDrawLooper::Builder的對象loopbuilder調用了addLayer()方法。
SkPaint* SkLayerDrawLooper::Builder::addLayer(const LayerInfo& info) {
    fCount += 1;

    Rec* rec = SkNEW(Rec);
    rec->fNext = fRecs;
    rec->fInfo = info;
    fRecs = rec;
    if (NULL == fTopRec) {
        fTopRec = rec;
    }

    return &rec->fPaint;
}

struct Rec {
        Rec*    fNext;
        SkPaint fPaint;
        LayerInfo fInfo;
    };

addLayer()函數首先創建一個Rec結構單鏈表節點,然後把不同的layerInfo插入到該節點中,最後返回該節點中的fPaint。可以看到init()函數中的for循環裏會設置這個fPaint的color、style、StrokeWidth和MaskFilter。設置完後loopBuilder使用detachLooper()方法把構造的SkLayerDrawLooper對象交給fLooper成員。

到這裏,fLooper中保存了四種不同的paint,因此在onDraw()中調用各種draw api時產生了四種不同圖形疊加到一起的效果。

但,在draw api中是draw looper是怎樣工作的呢?

可以拿onDraw()中的drawCircle()作爲切入點,看一下draw looper的到底是怎樣工作的。

void SkCanvas::drawCircle(SkScalar cx, SkScalar cy, SkScalar radius,
                          const SkPaint& paint) {
    if (radius < 0) {
        radius = 0;
    }

    SkRect  r;
    r.set(cx - radius, cy - radius, cx + radius, cy + radius);
    this->drawOval(r, paint);
}
void SkCanvas::drawOval(const SkRect& oval, const SkPaint& paint) {
    SkRect storage;
    const SkRect* bounds = NULL;
    if (paint.canComputeFastBounds()) { //判斷是否可以快速計算繪製邊界(主要判斷當前paint和skdrawlooper中的paint是否有mask)
        bounds = &paint.computeFastBounds(oval, &storage);
        if (this->quickReject(*bounds)) {
            return;
        }
    }


    LOOPER_BEGIN(paint, SkDrawFilter::kOval_Type, bounds)


    while (iter.next()) {
        iter.fDevice->drawOval(iter, oval, looper.paint());
    }


    LOOPER_END
}
從上面的代碼中可以看出,drawCircle()實際就是drawOval(),通過找出外切矩形來確定圓形的位置和形狀。

drawOval()函數可以看出做了三件事情:

1.計算繪製邊界;

2.外層循環AutoDrawLooper;

3.內層循環DrawIter。

在第一點中,由於drawOval()的參數中已經有了一個skrect,這可以看做一個初始的繪製邊界,之後這個初始邊界會被SkDrawLooper中所保存的paint去計算一些變換(比如maskfilter、patheffect),這些變換可能會改變最終的一個繪製邊界。如果繪製邊界爲空,或者爲無限,那就拒絕繪製。

第二點,從代碼中看LOOPER_BEGIN是一個宏定義,宏展開代碼如下:

#define LOOPER_BEGIN(paint, type, bounds)                           \
    this->predrawNotify();                                          \
    AutoDrawLooper  looper(this, paint, false, bounds);             \
    while (looper.next(type)) {                                     \
        SkAutoBounderCommit ac(fBounder);                           \
        SkDrawIter          iter(this);

#define LOOPER_END    }
宏展開後就可以很清楚的看到第一層循環,該循環的判斷條件是AutoDrawLooper對象,先看一下這個類的構造函數:

    AutoDrawLooper(SkCanvas* canvas, const SkPaint& paint,
                   bool skipLayerForImageFilter = false,
                   const SkRect* bounds = NULL) : fOrigPaint(paint) {
        fCanvas = canvas;
        fFilter = canvas->getDrawFilter();
        fPaint = NULL;
        fSaveCount = canvas->getSaveCount();
        fDoClearImageFilter = false;
        fDone = false;

        if (!skipLayerForImageFilter && fOrigPaint.getImageFilter()) {
            SkPaint tmp;
            tmp.setImageFilter(fOrigPaint.getImageFilter());
            (void)canvas->internalSaveLayer(bounds, &tmp, SkCanvas::kARGB_ClipLayer_SaveFlag,
                                            true, SkCanvas::kFullLayer_SaveLayerStrategy);
            // we'll clear the imageFilter for the actual draws in next(), so
            // it will only be applied during the restore().
            fDoClearImageFilter = true;
        }

        if (SkDrawLooper* looper = paint.getLooper()) {
            void* buffer = fLooperContextAllocator.reserveT<SkDrawLooper::Context>(
                    looper->contextSize());
            fLooperContext = looper->createContext(canvas, buffer);
            fIsSimple = false;
        } else {
            fLooperContext = NULL;
            // can we be marked as simple?
            fIsSimple = !fFilter && !fDoClearImageFilter;
        }
    }

在構造函數中可以直接去看第二個if語句,這個語句裏所做的事情是:如果paint設置了SkDrawLooper對象,則會在給定的一塊buffer創建一個context。如果paint設置的DrawLooper對象是SkLayerDrawLooper對象,則創建的context實際是LayerDrawLooperContext。在構造LayerDrawLooperContext時,它的成員是一個Rec結構指針fCurrRec,fCurrRec會指向paint中的SkLayerDrawLooper對象中的Rec結構鏈表頭。

我們再來看一下SkLayerDrawLooper中Rec這個結構體:(對於SkDrawLooper另一個子類暫時不分析)

    struct Rec {
        Rec*    fNext;
        SkPaint fPaint;
        LayerInfo fInfo;
    };
Rec鏈表節點保存着一個layerinfo和一個paint,其中layerinfo結構如下:

    /**
     *  Info for how to apply the layer's paint and offset.
     *
     *  fColorMode controls how we compute the final color for the layer:
     *      The layer's paint's color is treated as the SRC
     *      The draw's paint's color is treated as the DST
     *      final-color = Mode(layers-color, draws-color);
     *  Any SkXfermode::Mode will work. Two common choices are:
     *      kSrc_Mode: to use the layer's color, ignoring the draw's
     *      kDst_Mode: to just keep the draw's color, ignoring the layer's
     */
    struct SK_API LayerInfo {
        BitFlags            fPaintBits;
        SkXfermode::Mode    fColorMode;
        SkVector            fOffset;
        bool                fPostTranslate; //!< applies to fOffset
對於layerinfo成員fColorMode的解釋是:這個成員用來計算當前layer(這個layer指的的是效果層)的最終顏色,如果這個成員值爲kSrc_Mode,則使用當前layer's paint的顏色,且忽略要繪製layer's paint的顏色;如果值爲kDst_Mode,行爲相反。

對於成員fPaintBits,它的有關解釋在以下枚舉結構中:

/**
     *  Bits specifies which aspects of the layer's paint should replace the
     *  corresponding aspects on the draw's paint.
     *  kEntirePaint_Bits means use the layer's paint completely.
     *  0 means ignore the layer's paint... except for fColorMode, which is
     *  always applied.
     */
    enum Bits {
        kStyle_Bit      = 1 << 0,   //!< use this layer's Style/stroke settings
        kTextSkewX_Bit  = 1 << 1,   //!< use this layer's textskewx
        kPathEffect_Bit = 1 << 2,   //!< use this layer's patheffect
        kMaskFilter_Bit = 1 << 3,   //!< use this layer's maskfilter
        kShader_Bit     = 1 << 4,   //!< use this layer's shader
        kColorFilter_Bit = 1 << 5,  //!< use this layer's colorfilter
        kXfermode_Bit   = 1 << 6,   //!< use this layer's xfermode

        /**
         *  Use the layer's paint entirely, with these exceptions:
         *  - We never override the draw's paint's text_encoding, since that is
         *    used to interpret the text/len parameters in draw[Pos]Text.
         *  - Color is always computed using the LayerInfo's fColorMode.
         */
        kEntirePaint_Bits = -1

    };
fPaintBits用來判斷使用當前layer的Style/patheffect/maskfilter/shader/colorfilter/xfermode,還是使用即將要繪製的layer's paint。
對於成員fOffset和fPostTranslate,它們用來處理當前layer的位置偏移,會改變canvas的matrix。

因此,layerinfo保存了SkLayerDrawLooper中的每一個layer的paint mode flag和偏移信息。

然後回到之前的外層循環宏展開,構造完AutoDrawLooper對象looper,就會執行looper.next(type)。

    bool next(SkDrawFilter::Type drawType) {
        if (fDone) {
            return false;
        } else if (fIsSimple) {
            fDone = true;
            fPaint = &fOrigPaint;
            return !fPaint->nothingToDraw();
        } else {
            return this->doNext(drawType);
        }
    }
bool AutoDrawLooper::doNext(SkDrawFilter::Type drawType) {
    fPaint = NULL;
    SkASSERT(!fIsSimple);
    SkASSERT(fLooperContext || fFilter || fDoClearImageFilter);

    SkPaint* paint = fLazyPaint.set(fOrigPaint);

    if (fDoClearImageFilter) {
        paint->setImageFilter(NULL);
    }

    if (fLooperContext && !fLooperContext->next(fCanvas, paint)) {
        fDone = true;
        return false;
    }
    if (fFilter) {
        if (!fFilter->filter(paint, drawType)) {
            fDone = true;
            return false;
        }
        if (NULL == fLooperContext) {
            // no looper means we only draw once
            fDone = true;
        }
    }
    fPaint = paint;

    // if we only came in here for the imagefilter, mark us as done
    if (!fLooperContext && !fFilter) {
        fDone = true;
    }

    // call this after any possible paint modifiers
    if (fPaint->nothingToDraw()) {
        fPaint = NULL;
        return false;
    }
    return true;
}
考慮looper.next(type)執行到AutoDrawLooper::doNext()的情況,在doNext()第二個if語句中,會去執行fLooperContext->next(fCanvas, paint),這裏執行的就是剛剛構造的LayerDrawLooperContext對象中的next()方法:

bool SkLayerDrawLooper::LayerDrawLooperContext::next(SkCanvas* canvas,
                                                     SkPaint* paint) {
    canvas->restore();
    if (NULL == fCurrRec) {
        return false;
    }

    ApplyInfo(paint, fCurrRec->fPaint, fCurrRec->fInfo);

    canvas->save();
    if (fCurrRec->fInfo.fPostTranslate) {
        postTranslate(canvas, fCurrRec->fInfo.fOffset.fX,
                      fCurrRec->fInfo.fOffset.fY);
    } else {
        canvas->translate(fCurrRec->fInfo.fOffset.fX,
                          fCurrRec->fInfo.fOffset.fY);
    }
    fCurrRec = fCurrRec->fNext;

    return true;
}

看到這裏就比較明顯了,我們在drawLooper.cpp中的init()函數中定義的四種效果會在這裏進行處理。首先是在ApplyInfo()中處理我們定義的color、style、width和maskfilter;然後處理offset;最後fCurrRec指向下一個Rec節點。如果到了Rec鏈表尾,則外層循環結束。看一下ApplyInfo()設置的info的過程:

    void SkLayerDrawLooper::LayerDrawLooperContext::ApplyInfo(
        SkPaint* dst, const SkPaint& src, const LayerInfo& info) {

    dst->setColor(xferColor(src.getColor(), dst->getColor(), info.fColorMode));

    BitFlags bits = info.fPaintBits;
    SkPaint::TextEncoding encoding = dst->getTextEncoding();

    if (0 == bits) {
        return;
    }
    if (kEntirePaint_Bits == bits) {
        // we've already computed these, so save it from the assignment
        uint32_t f = dst->getFlags();
        SkColor c = dst->getColor();
        *dst = src;
        dst->setFlags(f);
        dst->setColor(c);
        dst->setTextEncoding(encoding);
        return;
    }

    if (bits & kStyle_Bit) {
        dst->setStyle(src.getStyle());
        dst->setStrokeWidth(src.getStrokeWidth());
        dst->setStrokeMiter(src.getStrokeMiter());
        dst->setStrokeCap(src.getStrokeCap());
        dst->setStrokeJoin(src.getStrokeJoin());
    }

    if (bits & kTextSkewX_Bit) {
        dst->setTextSkewX(src.getTextSkewX());
    }

    if (bits & kPathEffect_Bit) {
        dst->setPathEffect(src.getPathEffect());
    }
    if (bits & kMaskFilter_Bit) {
        dst->setMaskFilter(src.getMaskFilter());
    }
    if (bits & kShader_Bit) {
        dst->setShader(src.getShader());
    }
    if (bits & kColorFilter_Bit) {
        dst->setColorFilter(src.getColorFilter());
    }
    if (bits & kXfermode_Bit) {
        dst->setXfermode(src.getXfermode());
    }

    // we don't override these
#if 0
    dst->setTypeface(src.getTypeface());
    dst->setTextSize(src.getTextSize());
    dst->setTextScaleX(src.getTextScaleX());
    dst->setRasterizer(src.getRasterizer());
    dst->setLooper(src.getLooper());
    dst->setTextEncoding(src.getTextEncoding());
    dst->setHinting(src.getHinting());
#endif
}
對於這行:dst->setColor(xferColor(src.getColor(), dst->getColor(), info.fColorMode));

dst指的是draw api中的paint(跟着函數調用一層層傳下來),也就是即將要繪製的layer's paint ;src指的是SkLayerDrawLooper中Rec結構成員中的paint。dst設置當前paint的顏色時是根據layerinfo成員fColorMode決定的(如上面layerinfo中的註釋)。

我們回到DrawLooper這個例子,只拿繪製的圓形來說明:gParams數組定義的每組效果的顏色依次是白色,紅色,藍色,灰色;繪製圓形時先繪製白色的圓環(style=stroke),然後時紅色的圓環(style=stroke),之後是藍色的圓盤(style=full),最後是灰色的圓盤(style=full),這裏每次繪製都是繪製到一個layer上;由於每組效果的layerinfo成員fColorMode都設置的是kSrc_mode,因此這些layer上的圖案混合在一起的時候,在相互重疊的地方都保持的是繪製時當前layer的顏色。直觀的效果看上去就是後面繪製的圖案被之前的layer的圖案擋住,白色圓環蓋在了紅色圓環上,藍色圓盤的邊緣被上面兩層圖案蓋住,灰色圓盤被之前三層的圖案蓋住。

下面我們再看內存循環,先看SkDrawIter的構造函數:

    SkDrawIter(SkCanvas* canvas, bool skipEmptyClips = true) {
        canvas = canvas->canvasForDrawIter();
        fCanvas = canvas;
        canvas->updateDeviceCMCache();

        fClipStack = &canvas->fClipStack;
        fBounder = canvas->getBounder();
        fCurrLayer = canvas->fMCRec->fTopLayer;
        fSkipEmptyClips = skipEmptyClips;
    }

對於SkDrawIter類,它的基類是SkDraw;它的會在構造函數中爲每一層layer(這個layer指的是圖層)更新相對應的MCRec狀態(圖層鏈表DeviceCM中每一個layer與狀態棧中的棧幀MCRec有着一一對應關係,但有的棧幀MCRec可能沒有layer);這是爲了在正式繪製在layer上之前,調整好layer的空間關係(matrix)和剪裁區域(clip),後面正式開始繪製的時候都按照調整好的matrix和clip去繪製。

內存循環的判斷條件是iter.next():

     bool next() {
        // skip over recs with empty clips
        if (fSkipEmptyClips) {
            while (fCurrLayer && fCurrLayer->fClip.isEmpty()) {
                fCurrLayer = fCurrLayer->fNext;
            }
        }

        const DeviceCM* rec = fCurrLayer;
        if (rec && rec->fDevice) {

            fMatrix = rec->fMatrix;
            fClip   = &((SkRasterClip*)&rec->fClip)->forceGetBW();
            fRC     = &rec->fClip;
            fDevice = rec->fDevice;
            fBitmap = &fDevice->accessBitmap(true);
            fPaint  = rec->fPaint;
            SkDEBUGCODE(this->validate();)

            fCurrLayer = rec->fNext;
            if (fBounder) {
                fBounder->setClip(fClip);
            }
            // fCurrLayer may be NULL now

            return true;
        }
        return false;
    }

SkDrawIter類的next()方法的作用是:在正式繪製每一層layer之前,首先跳過clip爲空的layer(即clip爲空的layer不繪製);然後把當前要繪製的layer一些有用參數傳遞給SkDrawIter對象的成員,這些成員都是已經更新過matrix和clip狀態的,已經具備了繪製條件;最後判斷是否到了圖層鏈表尾,用於內層循環判斷條件。

從代碼中看出內層循環依然是對layer的MC狀態一些迭代更新,並在循環體中調用實際繪製函數去繪製當前狀態所依附的所有Layer,這裏與SkDrawLooper沒有關係。

總結:

看到這裏可以對SkDrawLooper(針對子類SkLayerDrawLooper)的作用作以下總結:

1.paint可比喻爲畫筆,畫筆可以畫出各種效果;這些效果會分佈在不同的效果層。SkLayerDrawLooper::LayerInfo定義效果層的paint mode flag和偏移;它決定了在繪製前使用當前效果層的paint效果還是使用即將繪製的paint效果,每一個paint對應的它對應的效果用Rec節點保存在SkLayerDrawLooper中,這個Rec結構可以認爲是一個效果層。

2.SkLayerDrawLooper::Builder的對象調用addLayer()函數首先創建Rec結構單鏈表節點,然後把不同的layerInfo插入到該節點中,最後返回每個節點中與新添加的layerinfo對應的fPaint。有了Rec結構鏈表,SkLayerDrawLooper::Builder會調用detachLooper()方法返回一個SkLayerDrawLooper對象,這個SkLayerDrawLooper對象可以設置到即將繪製的paint中。這裏的addLayer就是添加效果層。

3.把SkLayerDrawLooper對象設置給一個paint,當canvas調用draw api時會使用SkLayerDrawLooper對象去計算繪製邊界,然後在draw api的外層循環中使用SkLayerDrawLooper::LayerDrawLooperContext::next()函數去判斷使用即將繪製的paint效果還是looper中paint效果,並且會處理每一層的偏移。


對於二層循環總結如下:

1.外層循環的作用是判斷使用即將繪製的paint效果還是looper中paint效果,並且會處理每一層的偏移;

2.內層循環是在正式繪製在layer(這個layer是圖層)上之前,調整好layer的空間關係(matrix)和剪裁區域(clip),然後跳過clip爲空的layer,把當前要繪製的layer一些有用參數傳遞給SkDrawIter對象的成員,後面讓SkDrawIter中的Device去調用實際的繪製函數;這個過程依次迭代。

假設SkLayerDrawLooper對象爲looper,SkDrawIter對象爲iter,下面這張圖簡單的描述了兩層循環的行爲,綠色虛線內爲一次外層循環,紅色虛線爲一次內層循環。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章