skia draw bitmap flow

學習了jxt1234and2010的大作,試着用自己的理解去分析skia draw bitmap的過程,在這裏感謝jxt1234and2010

1.Draw bitmap api

這裏主要講一下在SkCanvas中,有哪些draw bitmap 的API可用,以及各自的作用是什麼。

draw bitmap的api有以下幾種:

  • drawBitmap將bitmap畫到x,y的位置(這本身是一個平移,需要和SkCanvas中的矩陣狀態疊加)。
  • drawBitmapRect 和 drawBitmapRectToRect將源圖src矩陣部分,畫到目標dst區域去。最後一個flags是AndroidL上爲了gpu繪製效果而加上去的,在CPU繪製中不需要關注。
  • drawSprite無視SkCanvas的矩陣狀態,將bitmap平移到x,y的位置。
  • drawBitmapMatrix繪製的bitmap帶有matrix的矩形變換,需要和SkCanvas的矩形變換疊加。
  • drawRect這個是最通用的方法,多用於需要加入額外效果的場景,比如需要繪製重複紋理。關於Tile的兩個參數就是OpenGL紋理貼圖中水平垂直方向上的邊界處理模式。

根據以下測試代碼來直觀看一下這些api的不同:

<pre name="code" class="cpp">SkBitmap src; 
SkImageDecoder::DecodeFile("E:/git/skia/Skia_VS2010/skia/out/2.png", &src); 

/*各種繪製圖片方法使用示例*/ 
{ 
canvas->drawBitmap(src, 0, 0, NULL); //在目的設備的(0,0)位置繪製解碼出的圖片bitmap
}


{ 
canvas->drawSprite(src, 400, 400, NULL); //簡易模式下,平移到目的設備的(400,400)處繪製解碼後的圖片
}

{ 
canvas->drawBitmap(src, 0, 0, NULL);
SkRect dstR; 
dstR.set(60,60,110,110); 
SkRect srcR; 
srcR.set(0,0,50,50); 
canvas->drawBitmapRectToRect(src, &srcR, dstR, NULL); //將源區域內的bitmap繪製到目的區域
}

{ 
SkMatrix m; 
m.setScale(2.3,0.9); 
canvas->drawBitmapMatrix(src, m, NULL); //按照m矩陣繪製bitmap
}


{ 
SkRect dstRect; 
dstRect.set(100,100,920,480); //繪製區域
SkPaint paint; 
SkMatrix m; 
m.setScale(2.3,0.9); 
SkShader* shader = SkShader::CreateBitmapShader(src, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &m); //重複貼圖
paint.setShader(shader); 
SkSafeUnref(shader); 
canvas->drawRect(dstRect, paint); 
}

2.Draw bitmap flow

通過調用SkCanvas的draw bitmap api之後,經過兩層循環就會進入 SkDraw類中的drawBitmap函數,這裏纔是drawBitmap的具體實現。


上面的圖中列出了draw bitmap api的繪製流程,可以看出drawBitmap基本可以分爲兩路:drawSprite和drawRect,即簡易繪製模式和繪製矩形區域。

下面借鑑這個流程來分析一下源碼。(實際源碼中我們在drawBitmap中還可以看到有第三條flow,drawBitmapAsMask。如果源bitmap屬於kAlpha_8_SkColorType類型纔會走這條flow,在這裏暫時忽略掉)

(1)drawSprite簡易模式

進入簡易模式有兩種方式:

a.直接去調用drawSprite;

b.調用drawBitmap時,如果bitmap的colorType不是kAlpha_8_SkColorType,同時對於bitmap的matrix只有平移操作,此時就會進入簡易模式的分支。

由於兩種方式的行爲類似,我們選擇從a方式源碼入手:

<pre name="code" class="cpp">void SkDraw::drawSprite(const SkBitmap& bitmap, int x, int y,
                        const SkPaint& origPaint) const {
    SkDEBUGCODE(this->validate();)

    // nothing to draw
    if (fRC->isEmpty() ||
            bitmap.width() == 0 || bitmap.height() == 0 ||
            bitmap.colorType() == kUnknown_SkColorType) {
        return;
    }

    SkIRect    bounds;
    bounds.set(x, y, x + bitmap.width(), y + bitmap.height()); //更新繪製範圍

    if (fRC->quickReject(bounds)) {
        return; // nothing to draw
    }

    SkPaint paint(origPaint);
    paint.setStyle(SkPaint::kFill_Style);

    if (NULL == paint.getColorFilter() && clipHandlesSprite(*fRC, x, y, bitmap)) {
        SkTBlitterAllocator allocator;
        // blitter will be owned by the allocator.
        SkBlitter* blitter = SkBlitter::ChooseSprite(*fBitmap, paint, bitmap,
                                                     x, y, &allocator);

        if (blitter) {
            if (fBounder && !fBounder->doIRect(bounds)) {
                return;
            }

            SkScan::FillIRect(bounds, *fRC, blitter);
            return;
        }
    }

    SkMatrix        matrix;
    SkRect          r;

    // get a scalar version of our rect
    r.set(bounds);

    // create shader with offset
    matrix.setTranslate(r.fLeft, r.fTop);
    SkAutoBitmapShaderInstall install(bitmap, paint, &matrix);
    const SkPaint& shaderPaint = install.paintWithShader();

    SkDraw draw(*this);
    matrix.reset();
    draw.fMatrix = &matrix;
    // call ourself with a rect
    // is this OK if paint has a rasterizer?
    draw.drawRect(r, shaderPaint);
} 


clipHandlesSprite(*fRC, x, y, bitmap)) 這個函數的作用是判斷當前剪裁區域是否可以執行簡易繪製模式。

它首先判斷當前剪裁區域需不需要考慮抗鋸齒:

如果不需要抗鋸齒,則執行簡易繪製模式;

如果需要抗鋸齒,則要判斷剪裁區域是否包含了繪製區域:

如果包含繪製區域,則執行簡易繪製模式;

如果不包含繪製區域,則執行drawRect方式。

SkCanvas的任何渲染都必須在裁剪區域之內,因此如果圖像跨越了裁剪區域邊界而且裁剪區域需要考慮抗鋸齒,在邊界上需要做特殊處理。

接下來就是使用SkBlitter::ChooseSprite()函數去創建簡易模式下的blitter,它是根據源bitmap和目的設備的colorType進行選擇的。

如果目的設備是kRGB_565_SkColorType,則需要使用SkSpriteBlitter::ChooseD16()工廠函數去創建具體類型的blitter,如果:

源bitmap是kN32_SkColorType,則創建Sprite_D16_S32_BlitRowProc類型的blitter(變量名中的D和S是dst和src);

源bitmap是kARGB_4444_SkColorType,且:

目的設備的alpha是不透明的,則創建Sprite_D16_S4444_Opaque;

目的設備的alpha是透明的,則創建Sprite_D16_S4444_Blend;

源bitmap是kRGB_565_SkColorType,且:

目的設備的alpha是不透明的,則創建Sprite_D16_S16_Opaque;

目的設備的alpha是透明的,則創建Sprite_D16_S16_Blend;

源bitmap是kIndex_8_SkColorType,且:

如果源bitmap是不透明的,且:

目的設備的alpha是不透明的,則創建Sprite_D16_SIndex8_Opaque;

目的設備的alpha是透明的,則創建Sprite_D16_SIndex8_Blend;

如果源bitmap是透明的,且:

目的設備的alpha是不透明的,則創建Sprite_D16_SIndex8A_Opaque;

目的設備的alpha是透明的,則創建Sprite_D16_SIndex8A_Blend。

如果目的設備是kN32_SkColorType,,則需要使用SkSpriteBlitter::ChooseD32()工廠函數去創建具體類型的blitter,過程與ChooseD16類似。

再下面就是SkScan::FillIRect(bounds, *fRC, blitter),掃描待繪製區域,然後使用所選的blitter渲染每個繪製區域。

<pre name="code" class="cpp">void SkScan::FillIRect(const SkIRect& r, const SkRasterClip& clip,
                       SkBlitter* blitter) {
    if (clip.isEmpty() || r.isEmpty()) {
        return;
    }

    if (clip.isBW()) { //不需要抗鋸齒
        FillIRect(r, &clip.bwRgn(), blitter);
        return;
    }

    /*需要抗鋸齒*/

    SkAAClipBlitterWrapper wrapper(clip, blitter);
    FillIRect(r, &wrapper.getRgn(), wrapper.getBlitter()); }

 

void SkScan::FillIRect(const SkIRect& r, const SkRegion* clip,
                       SkBlitter* blitter) {
    if (!r.isEmpty()) { //判斷繪製區域不是空的
        if (clip) {   //判斷剪裁區域不爲空
            if (clip->isRect()) {  //剪裁區域是一個單獨的矩形
                const SkIRect& clipBounds = clip->getBounds();

                if (clipBounds.contains(r)) { //如果剪裁矩形邊界包含繪製矩形,則直接渲染繪製矩形區域
                    blitrect(blitter, r);
                } else {  //如果剪裁矩形邊界不完全包含繪製矩形,則只渲染與剪裁區域相交的矩形區域
                    SkIRect rr = r;
                    if (rr.intersect(clipBounds)) {
                        blitrect(blitter, rr);
                    }
                }
            } else {  //走到這裏說明剪裁區域是由一個矩形序列組成,需要循環渲染每一個矩形
                SkRegion::Cliperator    cliper(*clip, r);
                const SkIRect&          rr = cliper.rect();

                while (!cliper.done()) {
                    blitrect(blitter, rr);
                    cliper.next();
                }
            }
        } else { //走到這說明沒有剪裁區域,則直接渲染繪製區域
            blitrect(blitter, r);
        }
    }
}


最後一步就是使用blitter將帶渲染區域渲染到目的設備上。 拿子類Sprite_D32_S32A_XferFilter中blitRect的實現來解釋實際blitter做了些什麼:

     virtual void blitRect(int x, int y, int width, int height) {
        SkASSERT(width > 0 && height > 0);
        uint32_t* SK_RESTRICT dst = fDevice->getAddr32(x, y); //找到目的設備的像素地址
        const uint32_t* SK_RESTRICT src = fSource->getAddr32(x - fLeft,
                                                             y - fTop); //找到源bitmap的像素地址
        size_t dstRB = fDevice->rowBytes();
        size_t srcRB = fSource->rowBytes();
        SkColorFilter* colorFilter = fColorFilter;
        SkXfermode* xfermode = fXfermode;

        do {
            const SkPMColor* tmp = src;

            /*如果有colorFilter,則需要對源bitmap進行顏色過濾處理*/

            if (NULL != colorFilter) {
                colorFilter->filterSpan(src, width, fBuffer);
                tmp = fBuffer;
            }

             /*爲目的設備計算每個像素混合處理之後的值,然後把得到的一行像素搬移到目的設備上;*/

            if (NULL != xfermode) {
                xfermode->xfer32(dst, tmp, width, NULL);
            } else {
                fProc32(dst, tmp, width, fAlpha);
            }

            dst = (uint32_t* SK_RESTRICT)((char*)dst + dstRB);
            src = (const uint32_t* SK_RESTRICT)((const char*)src + srcRB);
        } while (--height != 0);
    } 

在blitRect()函數中,主要做了三個工作:

1.在paint中設置的colorFilter在這裏完成處理;

2.在paint中設置的XferMode也在這裏進行圖像混合;圖像混合時,如果設置了XferMode,則在圖像混合時不使用alpha;如果沒有設置XferMode,則使用源bitmap的Alpha去處理混合。

3.處理完的bitmap搬移到目的設備上。

整個簡易模式flow流程如下:

(2)drawRect

如果繪製時需要使用paint加入各種渲染效果,就要使用drawRect。進入drawRect flow的方式有兩種:

1.直接調用;

2.調用drawBitmap或者drawSprite時不滿足簡易模式的條件。

我們根據上面演示的例子來解釋。

{ 
SkRect dstRect; 
dstRect.set(100,100,920,480); //繪製區域
SkPaint paint; 
SkMatrix m; 
m.setScale(2.3,0.9); //設置仿射變換矩陣的縮放元素
SkShader* shader = SkShader::CreateBitmapShader(src, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &m); //重複貼圖
paint.setShader(shader); 
SkSafeUnref(shader); 
canvas->drawRect(dstRect, paint); 
}

代碼中先設置了一個(100,100,920,480)矩形繪製區域,然後設置仿射變換矩陣的縮放比例(2.3,0.9),最後爲paint添加了一個shader,shader設置爲重複模式。

shader中的平鋪模式有三種:CLAMP(越界時限制在邊界)、REPEAT(越界時從開頭取起)、MIRROR(越界時取樣方向倒轉去取)。

設置完shader後就開始drawRect。

void SkDraw::drawRect(const SkRect& rect, const SkPaint& paint) const {
    SkDEBUGCODE(this->validate();)

    // nothing to draw
    if (fRC->isEmpty()) {
        return;
    }

    SkPoint strokeSize;
    RectType rtype = ComputeRectType(paint, *fMatrix, &strokeSize);

    if (kPath_RectType == rtype) {
        SkPath  tmp;
        tmp.addRect(rect);
        tmp.setFillType(SkPath::kWinding_FillType);
        this->drawPath(tmp, paint, NULL, true);
        return;
    }

    const SkMatrix& matrix = *fMatrix;
    SkRect          devRect;

    // transform rect into devRect
    matrix.mapPoints(rect_points(devRect), rect_points(rect), 2);
    devRect.sort();

    if (fBounder && !fBounder->doRect(devRect, paint)) {
        return;
    }

    // look for the quick exit, before we build a blitter
    SkIRect ir;
    devRect.roundOut(&ir);
    if (paint.getStyle() != SkPaint::kFill_Style) {
        // extra space for hairlines
        ir.inset(-1, -1);
    }
    if (fRC->quickReject(ir)) {
        return;
    }

    SkDeviceLooper looper(*fBitmap, *fRC, ir, paint.isAntiAlias());
    while (looper.next()) {
        SkRect localDevRect;
        looper.mapRect(&localDevRect, devRect);
        SkMatrix localMatrix;
        looper.mapMatrix(&localMatrix, matrix);

        SkAutoBlitterChoose blitterStorage(looper.getBitmap(), localMatrix,
                                           paint);
        const SkRasterClip& clip = looper.getRC();
        SkBlitter*          blitter = blitterStorage.get();

        // we want to "fill" if we are kFill or kStrokeAndFill, since in the latter
        // case we are also hairline (if we've gotten to here), which devolves to
        // effectively just kFill
        switch (rtype) {
            case kFill_RectType:
                if (paint.isAntiAlias()) {
                    SkScan::AntiFillRect(localDevRect, clip, blitter);
                } else {
                    SkScan::FillRect(localDevRect, clip, blitter);
                }
                break;
            case kStroke_RectType:
                if (paint.isAntiAlias()) {
                    SkScan::AntiFrameRect(localDevRect, strokeSize, clip, blitter);
                } else {
                    SkScan::FrameRect(localDevRect, strokeSize, clip, blitter);
                }
                break;
            case kHair_RectType:
                if (paint.isAntiAlias()) {
                    SkScan::AntiHairRect(localDevRect, clip, blitter);
                } else {
                    SkScan::HairRect(localDevRect, clip, blitter);
                }
                break;
            default:
                SkDEBUGFAIL("bad rtype");
        }
    }
}

drawRect的剪裁區域如果爲空則不會繼續繪製。如果不是空的話,drawRect會首先計算rect類型,skia中的rect類型有四種:

enum RectType {
kHair_RectType, //無線寬stroke
kFill_RectType, //矩形填充
kStroke_RectType,//矩形輪廓
kPath_RectType //path
};

對於kPath_RectType屬於drawPath範圍,以後再解釋,其它三種屬於drawRect。

判斷完rect類型之後會把繪製矩形區域進行仿射變換,映射到目的設備的矩形區域中。

接下來會定義一個SkDeviceLooper 對象,根據SkDeviceLooper .h中的註釋:

Helper class to manage "tiling" a large coordinate space into managable
chunks, where managable means areas that are <= some max critical coordinate size.

大概的意思是,這個類用來把比較大的座標空間分成一些“易處理"的數據塊,這些數據塊都不大於一個最大值。這個最大值根據是否

採用抗鋸齒來判斷是多少,並把由這個最大值構成的矩形區域叫做delta區域:

enum Delta {
kBW_Delta = 1 << 14, // 16K, gives room to spare for fixedpoint
kAA_Delta = kBW_Delta >> 2 // supersample 4x
};

SkDeviceLooper 的構造函數中會去記錄繪製區域與剪切區域的相交區域:

如果剪切區域爲空,或者相交區域爲空,標記爲kDone_State;

如果相交區域不爲空,且相交區域在delta區域內,則標記爲kSimple_State;否則標爲kComplex_State。

SkDeviceLooper的next()函數會根據源bitmap的這三種狀態,爲looper對象返回一個有效的managable chunks。

然後在while()每一次循環中,被處理的對象爲每一個chunk。

後面的就要爲源bitmap的每一個子集(即chunk)使用SkBlitter::Choose()去選擇blitter,根據Canvas綁定的Bitmap像素模式和paint屬性去選擇blitter。
繪製圖片時paint有Shader(SkBitmapProcShader),因此是選的是帶Shader的Blitter,比如適應ARGB格式的 SkARGB32_Shader_Blitter。得到blitter之後就需要根據不同的rect類型進行渲染操作。

類似drawSprit,在渲染之前需要掃描待繪製矩形區域,這裏會根據不同的rect類型去選擇不同的函數:

    switch (rtype) {
            case kFill_RectType:
                if (paint.isAntiAlias()) {
                    SkScan::AntiFillRect(localDevRect, clip, blitter);
                } else {
                    SkScan::FillRect(localDevRect, clip, blitter);
                }
                break;
            case kStroke_RectType:
                if (paint.isAntiAlias()) {
                    SkScan::AntiFrameRect(localDevRect, strokeSize, clip, blitter);
                } else {
                    SkScan::FrameRect(localDevRect, strokeSize, clip, blitter);
                }
                break;
            case kHair_RectType:
                if (paint.isAntiAlias()) {
                    SkScan::AntiHairRect(localDevRect, clip, blitter);
                } else {
                    SkScan::HairRect(localDevRect, clip, blitter);
                }
                break;
            default:
                SkDEBUGFAIL("bad rtype");
        }

每種rect類型這裏都會判斷是否考慮抗鋸齒,並採用不同的函數實際去處理。這裏只討論kFill_RectType。

如果不考慮抗鋸齒的話,後面的流程與drawSprite這部分過程類似。

如果考慮抗鋸齒,需要在blit之前處理抗鋸齒,基本方法就是對浮點的座標,按其離整數的偏離度給一個alpha權重,將顏色乘以此權重(減淡顏色)畫上去。

SkScan中在繪製矩形時,先用blitV繪製左右邊界,再用blitAntiH繪製上下邊界,中間大塊的不需要考慮抗鋸齒,因而用blitRect。

後面blit的過程可以參考skia圖片繪製的實現(2) 和我後來補充的另一篇skia bitmap shader

整個flow:


fill rect flow:


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