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区域和最后绘制的区域为:

最后显示的结果:

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值