本次學習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,下面這張圖簡單的描述了兩層循環的行爲,綠色虛線內爲一次外層循環,紅色虛線爲一次內層循環。