本文主要是介绍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
上面的图中列出了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 drawif (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 rectr.set(bounds);// create shader with offsetmatrix.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 drawif (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 devRectmatrix.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 blitterSkIRect ir;devRect.roundOut(&ir);if (paint.getStyle() != SkPaint::kFill_Style) {// extra space for hairlinesir.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 kFillswitch (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:
这篇关于skia draw bitmap flow的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!