skia draw bitmap flow

2024-01-24 01:08
文章标签 flow draw bitmap skia

本文主要是介绍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 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的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/638101

相关文章

GNSS CTS GNSS Start and Location Flow of Android15

目录 1. 本文概述2.CTS 测试3.Gnss Flow3.1 Gnss Start Flow3.2 Gnss Location Output Flow 1. 本文概述 本来是为了做Android 14 Gnss CTS 的相关环境的搭建和测试,然后在测试中遇到了一些问题,去寻找CTS源码(/cts/tests/tests/location/src/android/locat

数据流与Bitmap之间相互转换

把获得的数据流转换成一副图片(Bitmap) 其原理就是把获得倒的数据流序列化到内存中,然后经过加工,在把数据从内存中反序列化出来就行了。 难点就是在如何实现加工。因为Bitmap有一个专有的格式,我们常称这个格式为数据头。加工的过程就是要把这个数据头与我们之前获得的数据流合并起来。(也就是要把这个头加入到我们之前获得的数据流的前面)      那么这个头是

Understanding the GitHub Flow

这里看下Github的入门介绍    --链接 GitHub Flow is a lightweight, branch-based workflow that supports teams and projects where deployments are made regularly. This guide explains how and why GitHub Flow works

Matlab draw a sector 画一个扇形

Matlab draw a sector 画一个扇形 Matlab的函数代码: function [ sector ] = Draw_a_sector( map, center,StartR, EndR, StartAngle, EndAngle )%% Get indexs(row,column)size_map=size(map);for i = 1:size_map(2)index

数据标注:批量转换json文件,出现AttributeError: module ‘labelme.utils‘ has no attribute ‘draw_label‘错误

labelme版本更换为3.11.2 "D:\Anaconda3\Lib\site-packages\labelme\utils\draw.py"缺失?: import ioimport os.path as ospimport numpy as npimport PIL.Imageimport PIL.ImageDrawimport PIL.ImageFontdef label_co

黑马点评10——用户签到-BitMap数据结构

文章目录 BitMap用法签到功能签到统计 BitMap用法 其实数据库完全可以实现签到功能 但签到数据比较大,借鉴签到卡的思想 布隆过滤器也是使用BitMap实现的. 签到功能 因为是当前用户的当天,所以保存需要的年月日不需要参数,可以直接获取。 @Overridepublic Result sign() {// 1. 获取当前登录用户Long userId

Versioned Staged Flow-Sensitive Pointer Analysis

VSFS 1.Introduction2.Approach2.1.相关概念2.2.VSFS 3.Evaluation参考文献 1.Introduction 上一篇blog我介绍了目前flow-sensitive pointer analysis常用的SFS算法。相比IFDS-based方法,SFS显著通过稀疏分析提升了效率,但是其内部依旧有许多冗余计算,留下了很大优化空间。 以

将DIB/bitmap读入内存并转为 halcon hobject

问题由来:在mfc halcon混合编程中,发现halcon::readimage() 函数读取图片(8位8M/bmp)至少200ms,当然24位 32位bmp 倍数所消耗的时间倍数上涨。那么有没有什么方法加快读取速度?目前发现一个亲测可行的方式:  1、通过 DIBAPI 读取图片,下载可转到点击打开链接,赚点积分 2、获取所读读片的图像数据的首地址,注意非结构头地址 3、通过halcon

C#Bitmap和Image之间的关系

Image 类 Image 是一个抽象基类,它定义了所有图像类型的共同属性和方法。它提供了图像处理的通用接口,比如获取图像的尺寸、像素格式、帧数等。Image 类本身不能被实例化,它只是提供了一个通用的框架,具体的图像类型(如位图、图标、元文件等)需要通过继承 Image 类来实现。Image 类提供了一些通用的方法,如 Save(保存图像到文件)、GetThumbnailImage(获取图像的

Android Drawable与Bitmap

一、相关概念 1、Drawable就是一个可画的对象,其可能是一张位图(BitmapDrawable),也可能是一个图形(ShapeDrawable),还有可能是一个图层(LayerDrawable),我们根据画图的需求,创建相应的可画对象 2、Canvas画布,绘图的目的区域,用于绘图 3、Bitmap位图,用于图的处理 4、Matrix矩阵 二、Bitmap 1、从资源中获取