skia bitmap shader

在繪製bitmap的時候經常會用到bitmapShader工廠函數創建不同的shader,shader在後面的過程中對源bitmap着色處理。

一個簡單使用例子如下:

   SkBitmap src; 
   SkImageDecoder::DecodeFile("E:/git/skia/Skia_VS2010/skia/out/2.png", &src);  //把圖片解碼到源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);

1.shader創建

在這個例子中只關注SkShader::CreateBitmapShader()這一行,我們在SkShader.cpp看一下它的實現:

SkShader* SkShader::CreateBitmapShader(const SkBitmap& src, TileMode tmx, TileMode tmy,
                                       const SkMatrix* localMatrix) {
    return ::CreateBitmapShader(src, tmx, tmy, localMatrix, NULL);
  }

這裏會調用SkBitmapProcShader.cpp中的CreateBitmapShader()函數,由它去具體創建shader:

SkShader* CreateBitmapShader(const SkBitmap& src, SkShader::TileMode tmx,
        SkShader::TileMode tmy, const SkMatrix* localMatrix, SkTBlitterAllocator* allocator) {
    SkShader* shader;
    SkColor color;
    if (src.isNull() || bitmapIsTooBig(src)) {
        if (NULL == allocator) {
            shader = SkNEW(SkEmptyShader);
        } else {
            shader = allocator->createT<SkEmptyShader>();
        }
    }
    else if (canUseColorShader(src, &color)) {
        if (NULL == allocator) {
            shader = SkNEW_ARGS(SkColorShader, (color));
        } else {
            shader = allocator->createT<SkColorShader>(color);
        }
    } else {
        if (NULL == allocator) {
            shader = SkNEW_ARGS(SkBitmapProcShader, (src, tmx, tmy, localMatrix));
        } else {
            shader = allocator->createT<SkBitmapProcShader>(src, tmx, tmy, localMatrix);
        }
    }
    return shader;
}

這裏的flow是:

如果源bitmap沒有像素數據且超過了寬高超過了64k,就創建一個SkEmptyShader;

上面條件不具備就判斷如果是否可以創建SkColorShader;

如果也不可以創建SkColorShader,就需要去創建一個SkBitmapProcShader。

我們例子最終會創建一個SkBitmapProcShader對象,看一下它的實現:

SkBitmapProcShader::SkBitmapProcShader(const SkBitmap& src, TileMode tmx, TileMode tmy,
                                       const SkMatrix* localMatrix)
        : INHERITED(localMatrix) {
    fRawBitmap = src;
    fTileModeX = (uint8_t)tmx;
    fTileModeY = (uint8_t)tmy;
}

這裏很簡單,只是簡單的賦值操作,這樣就完成了一個shader的創建。


2.創建shader context

在skia draw bitmap flow的分析中,創建blittr是在drawRect中進行:

SkAutoBlitterChoose blitterStorage(looper.getBitmap(), localMatrix,
                                           paint);

SkAutoBlitterChoose(const SkBitmap& device, const SkMatrix& matrix,
                        const SkPaint& paint, bool drawCoverage = false) {
        fBlitter = SkBlitter::Choose(device, matrix, paint, &fAllocator,
                                     drawCoverage);
    }

這裏使用了SkBlitter::Choose()工廠函數。這裏的代碼比較長,只介紹重點部分:

    /*
     *  We create a SkShader::Context object, and store it on the blitter.
     */
    SkShader::Context* shaderContext;
    if (shader) {
        SkShader::ContextRec rec(device, *paint, matrix);  //SkShader::Context要用的參數對象
        // Try to create the ShaderContext
        void* storage = allocator->reserveT<SkShader::Context>(shader->contextSize());   //爲SkShader::Context申請一塊buffer
        shaderContext = shader->createContext(rec, storage);  //在這個buffer上創建SkShader::Context對象
        if (!shaderContext) {
            allocator->freeLast();
            blitter = allocator->createT<SkNullBlitter>();
            return blitter;
        }
        SkASSERT(shaderContext);
        SkASSERT((void*) shaderContext == storage);
    } else {
        shaderContext = NULL;
    }

這部分code主要是創建創建SkShader::Context對象,並把它保存在blitter中。由於這裏的shader對象實際是SkBitmapProcShader類型,因此會調用SkBitmapProcShader::onCreateContext()

去創建SkShader::Context對象。

SkShader::Context* SkBitmapProcShader::onCreateContext(const ContextRec& rec, void* storage) const {
    if (!fRawBitmap.getTexture() && !valid_for_drawing(fRawBitmap)) {
        return NULL;
    }

    SkMatrix totalInverse;
    // Do this first, so we know the matrix can be inverted.
    if (!this->computeTotalInverse(rec, &totalInverse)) {
        return NULL;
    }

    void* stateStorage = (char*)storage + sizeof(BitmapProcShaderContext);
    SkBitmapProcState* state = SkNEW_PLACEMENT(stateStorage, SkBitmapProcState);

    SkASSERT(state);
    state->fTileModeX = fTileModeX;
    state->fTileModeY = fTileModeY;
    state->fOrigBitmap = fRawBitmap;
    if (!state->chooseProcs(totalInverse, *rec.fPaint)) {
        state->~SkBitmapProcState();
        return NULL;
    }

    return SkNEW_PLACEMENT_ARGS(storage, BitmapProcShaderContext, (*this, rec, state));
}

在SkBitmapProcShader::onCreateContext()中,實際只做了兩件事:創建了SkBitmapProcState類對象和BitmapProcShaderContext對象。

2.1 SkBitmapProcState::chooseProcs

SkBitmapProcState類定義在SkBitmapProcState.h中。它在構造函數中什麼也不做,在SkBitmapProcShader::onCreateContext()中會調用它的chooseProcs()函數。

當bitmapShader在爲像素着色時,需要計算要繪製的像素座標生成座標集,然後對座標集採樣合成。

這個函數主要是:

1、(優化步驟)在大於SkPaint::kLow_FilterLevel的質量要求下,試圖做預縮放。

首先去做一個預處理,這個預處理時根據設置的filter_level不同進行不同的處理,skia中的filter_level有:

enum FilterLevel {
      kNone_FilterLevel,
      kLow_FilterLevel,
      kMedium_FilterLevel,
      kHigh_FilterLevel 
};

對於前兩種類型不會進行預處理,對於kHigh_FilterLevel 類型滿足一定條件(見源碼)的情況下會做卷積運算等處理,然後縮放圖片,移除matrix中scale元素;如果設置爲kHigh_FilterLevel類型不滿足上面條件則與kMedium_FilterLevel處理一致。

kMedium_FilterLevel類型在預處理時會進行mipmap處理。預處理結束後,高filter level會降爲low level統一處理。
2、預處理結束後會根據shader平鋪模式,matrix變換類型,是否需要過濾來設置適當的計算座標回調函數MatrixProc。選擇matrix函數:chooseMatrixProc。
3、根據源目的bitmap的colorType選擇合適的採樣回調函數SampleProc:
(1)高質量:setBitmapFilterProcs
(2)kLow_FilterLevel或kNone_FilterLevel:採取flags計算的方法,根據x,y變化矩陣情況和採樣要求選擇函數
4、(優化步驟)在滿足條件時,選取shaderProc函數,此函數替代matrix和sample函數可起性能優化作用
5、(優化步驟)platformProcs(),進一步選擇優化版本的sample函數

對影響這個flow的幾個因素做簡單一下分析:

(1).matrix座標變換矩陣

這是一個3X3的矩陣,可以使像素座標進行平移、縮放、旋轉、錯切、透視投影變換。skia源碼中定義了以下變換類型:

    enum TypeMask {
        kIdentity_Mask      = 0,           //單位矩陣,不發生變化
        kTranslate_Mask     = 0x01,  //!< set if the matrix has translation
        kScale_Mask         = 0x02,  //!< set if the matrix has X or Y scale
        kAffine_Mask        = 0x04,  //!< set if the matrix skews or rotates
        kPerspective_Mask   = 0x08   //!< set if the matrix is in perspective
    }; 

與剛剛敘述的變化對應見下圖:

如果shader中設置的matrix是kIdentity_Mask或kTranslate_Mask類型的變換,它們不會對源bitmap的形狀發生變化;

如果shader中設置的matrix是kScale_Mask或kAffine_Mask或kPerspective_Mask類型的變換,它們會對源比bitmap的形狀產生變化,這種情況下對於像素座標重新計算和採樣會比較複雜。

(2).TileMode平鋪模式

如果待繪製的區域大於源bitmap的大小,當繪製到bitmap的邊界時需要考慮邊界之後如何繪製:

clamp:邊界後的像素顏色與邊界顏色一致;

repeat:繪製到邊界後從重複繪製bitmap;

mirror:繪製到邊界後反向繪製bitmap。

(3).filter

暫時沒看到

對於shader中設置的matrix是kScale_Mask或kAffine_Mask或kPerspective_Mask類型的變換的情況時,選擇matrixPrco和sampleProc過程如下圖:

對於計算座標的回調函數MatrixProc選擇過程如下:

對於sampleProc的選擇:

如果是kHigh_FilterLevel類型,使用setBitmapFilterProcs設置高級插值算法,設置 shaderProc 爲 highQualityFilter32/highQualityFilter16(也就是獨立計算座標和採樣像素),取代matrixProc和sampleProc。

如果是kLow_FilterLevel或kNone_FilterLevel:採取flags計算的方法,根據x,y變化矩陣情況和採樣要求選擇函數,如下圖:

那在我們的例子中,我們沒有設置filter level,那默認是 kNone_FilterLevel,因此不需要預處理;matrix會進行scale變換,tile mode在x,y方向都是repeat,沒有設置filter,因此matrixProc爲NoFilterProc_Scale<RepeatTileProcs, false>函數;源/目的設備bitmap類型爲32,paint alpha默認爲不透明,因此sampleProc爲S32_opaque_D32_nofilter_DX函數。

2.2 BitmapProcShaderContext

SkBitmapProcShader::onCreateContext()做的另一件事就是new了一個BitmapProcShaderContext對象,BitmapProcShaderContext在構造函數中主要是更新fFlags,以下是各種flag類型:

enum Flags {
        //!< set if all of the colors will be opaque
        kOpaqueAlpha_Flag  = 0x01,

        //! set if this shader's shadeSpan16() method can be called
        kHasSpan16_Flag = 0x02,

        /** Set this bit if the shader's native data type is instrinsically 16
            bit, meaning that calling the 32bit shadeSpan() entry point will
            mean the the impl has to up-sample 16bit data into 32bit. Used as a
            a means of clearing a dither request if the it will have no effect
        */
        kIntrinsicly16_Flag = 0x04,

        /** set if the spans only vary in X (const in Y).
            e.g. an Nx1 bitmap that is being tiled in Y, or a linear-gradient
            that varies from left-to-right. This flag specifies this for
            shadeSpan().
         */
        kConstInY32_Flag = 0x08,

        /** same as kConstInY32_Flag, but is set if this is true for shadeSpan16
            which may not always be the case, since shadeSpan16 may be
            predithered, which would mean it was not const in Y, even though
            the 32bit shadeSpan() would be const.
         */
        kConstInY16_Flag = 0x10
    }; 

3.選擇blitter

創建完shader context後就會根據canvas中的目的設備的colorType去選擇blitter:

    switch (device.colorType()) {
        case kAlpha_8_SkColorType:
            if (drawCoverage) {
                SkASSERT(NULL == shader);
                SkASSERT(NULL == paint->getXfermode());
                blitter = allocator->createT<SkA8_Coverage_Blitter>(device, *paint);
            } else if (shader) {
                blitter = allocator->createT<SkA8_Shader_Blitter>(device, *paint, shaderContext);
            } else {
                blitter = allocator->createT<SkA8_Blitter>(device, *paint);
            }
            break;

        case kRGB_565_SkColorType:
            blitter = SkBlitter_ChooseD565(device, *paint, shaderContext, allocator);
            break;

        case kN32_SkColorType:
            if (shader) {
                blitter = allocator->createT<SkARGB32_Shader_Blitter>(
                        device, *paint, shaderContext);
            } else if (paint->getColor() == SK_ColorBLACK) {
                blitter = allocator->createT<SkARGB32_Black_Blitter>(device, *paint);
            } else if (paint->getAlpha() == 0xFF) {
                blitter = allocator->createT<SkARGB32_Opaque_Blitter>(device, *paint);
            } else {
                blitter = allocator->createT<SkARGB32_Blitter>(device, *paint);
            }
            break;

        default:
            SkDEBUGFAIL("unsupported device config");
            blitter = allocator->createT<SkNullBlitter>();
            break;
    }


我們的例子中目的設備的colorType是kN32_SkColorType,並且設置了bitmapShader,所以實際的blitter對象是SkARGB32_Shader_Blitter類型。

可以看一下SkARGB32_Shader_Blitter,它的原型是在SkCoreBlitter.h中,成員函數實現是在SkBlitter_ARGB32.cpp中。構造函數爲:

SkARGB32_Shader_Blitter::SkARGB32_Shader_Blitter(const SkBitmap& device,
        const SkPaint& paint, SkShader::Context* shaderContext)
    : INHERITED(device, paint, shaderContext)
{
    fBuffer = (SkPMColor*)sk_malloc_throw(device.width() * (sizeof(SkPMColor))); //爲目的設備申請一行像素所佔size的buffer

    fXfermode = paint.getXfermode();
    SkSafeRef(fXfermode);

    int flags = 0;
    if (!(shaderContext->getFlags() & SkShader::kOpaqueAlpha_Flag)) {
        flags |= SkBlitRow::kSrcPixelAlpha_Flag32;
    }
    // we call this on the output from the shader
    fProc32 = SkBlitRow::Factory32(flags);
    // we call this on the output from the shader + alpha from the aa buffer
    fProc32Blend = SkBlitRow::Factory32(flags | SkBlitRow::kGlobalAlpha_Flag32);

    fShadeDirectlyIntoDevice = false;
    if (fXfermode == NULL) {
        if (shaderContext->getFlags() & SkShader::kOpaqueAlpha_Flag) {
            fShadeDirectlyIntoDevice = true;
        }
    } else {
        SkXfermode::Mode mode;
        if (fXfermode->asMode(&mode)) {
            if (SkXfermode::kSrc_Mode == mode) {
                fShadeDirectlyIntoDevice = true;
                fProc32Blend = blend_srcmode;
            }
        }
    }

    fConstInY = SkToBool(shaderContext->getFlags() & SkShader::kConstInY32_Flag);
}

在構造函數中主要是根據BitmapProcShaderContext的flags去設置blitRow回調函數和blitRow blend回調函數,這兩個函數會根據平臺不同而不同,我們是在VS2010中調試,因此這兩個函數分別爲S32_Opaque_BlitRow32和S32_Blend_BlitRow32_SSE2。這裏還會根據XferMode是否設置來設置fShadeDirectlyIntoDevice = true。

4.blit過程中shader處理

前的過程都是在爲shader處理做指令配置,當掃描出待繪製的矩形區域後,就要進行blit處理。以我們的例子,從選擇完blitter到blit實施需要走以下call flow:

SkScan::FillRect(localDevRect, clip, blitter)->SkScan::FillRect(const SkRect& r, const SkRegion* clip, SkBlitter* blitter) ->SkScan::FillIRect(const SkIRect& r, const SkRegion* clip,SkBlitter* blitter)->SkARGB32_Shader_Blitter::blitRect(int x, int y, int width, int height).

由於fShadeDirectlyIntoDevice = true,所以SkARGB32_Shader_Blitter::blitRect中的處理只需要去看以下部分:

SkScan::FillRect(localDevRect, clip, blitter)->SkScan::FillRect(const SkRect& r, const SkRegion* clip, SkBlitter* blitter) ->SkScan::FillIRect(const SkIRect& r, const SkRegion* clip,SkBlitter* blitter)->SkARGB32_Shader_Blitter::blitRect(int x, int y, int width, int height).

由於fShadeDirectlyIntoDevice = true,所以SkARGB32_Shader_Blitter::blitRect中的處理只需要去看以下部分:

    if (fShadeDirectlyIntoDevice) {
        void* ctx;
        SkShader::Context::ShadeProc shadeProc = shaderContext->asAShadeProc(&ctx);
        if (shadeProc) {
            do {
                shadeProc(ctx, x, y, device, width);
                y += 1;
                device = (uint32_t*)((char*)device + deviceRB);
            } while (--height > 0);
        } else {
            do {
                shaderContext->shadeSpan(x, y, device, width);
                y += 1;
                device = (uint32_t*)((char*)device + deviceRB);
            } while (--height > 0);
        }
    } 

由於shader context沒有設置shadeProc,因此直接看else部分,這裏會調用到BitmapProcShaderContext::shadeSpan,並且每一次調用時針對每一行去處理。看一下這個函數具體做了什麼:

void SkBitmapProcShader::BitmapProcShaderContext::shadeSpan(int x, int y, SkPMColor dstC[],
                                                            int count) {
    const SkBitmapProcState& state = *fState;
    if (state.getShaderProc32()) {  //如果有shaderProc可以直接用,我們的例子中不會設置這個回調
        state.getShaderProc32()(state, x, y, dstC, count);
        return;
    }

    uint32_t buffer[BUF_MAX + TEST_BUFFER_EXTRA];  //下面一次循環所用 本地buffer,用來放置座標變換後的像素座標值,對於_nofilter_DX,buffer座標放置方式:y32, x16, x16, x16, x16, x16...
    SkBitmapProcState::MatrixProc   mproc = state.getMatrixProc(); //取得SkBitmapProcState中設置的MatrixProc,例子中是NoFilterProc_Scale<RepeatTileProcs, false>
    SkBitmapProcState::SampleProc32 sproc = state.getSampleProc32(); //取得SkBitmapProcState中設置的SampleProc32,例子中是sampleProc爲S32_opaque_D32_nofilter_DX
    int max = state.maxCountForBufferSize(sizeof(buffer[0]) * BUF_MAX); //計算每次循環可以放置幾個像素座標值

    SkASSERT(state.fBitmap->getPixels());
    SkASSERT(state.fBitmap->pixelRef() == NULL ||
             state.fBitmap->pixelRef()->isLocked());

    for (;;) {
        int n = count;     //count爲目的設備bitmap一行的像素數
        if (n > max) {
            n = max;      //count超過max,則每次處理max個像素座標
        }
        SkASSERT(n > 0 && n < BUF_MAX*2);
        mproc(state, buffer, n, x, y);   //使用NoFilterProc_Scale<RepeatTileProcs, false>函數計算變換後的座標值
        sproc(state, buffer, n, dstC);   //調用sampleProc爲S32_opaque_D32_nofilter_DX根據計算出來的座標值,把源bitmap中對應像素的顏色依次填充到目的bitmap對應座標的像素中

        if ((count -= n) == 0) {        //處理完一行break
            break;
        }
        SkASSERT(count > 0);
        x += n;
        dstC += n;
    }
}

這個flow可以用下圖來描述:


所有的像素全部shaderSpan之後,目的設備中的bitmap區域和最後繪製的區域爲:

最後顯示的結果:

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