您的位置:首页 > 移动开发 > Android开发

Skia深入分析3——skia图片绘制的实现(1)

2015-10-15 00:00 381 查看
此篇讲Skia绘制图片的流程,在下一篇讲图像采样原理、混合和抖动技术

1、API用法

(1)drawBitmap

void drawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top, const SkPaint* paint = NULL);

将bitmap画到x,y的位置(这本身是一个平移,需要和SkCanvas中的矩阵状态叠加)。

(2)drawBitmapRect 和 drawBitmapRectToRect

void drawBitmapRect(const SkBitmap& bitmap, const SkRect& dst, const SkPaint* paint = NULL);

void drawBitmapRectToRect(const SkBitmap& bitmap, const SkRect* src, const SkRect& dst, const SkPaint* paint, DrawBitmapRectFlags flags);

将源图src矩阵部分,画到目标dst区域去。

最后一个flags是AndroidL上为了gpu绘制效果而加上去的,在CPU绘制中不需要关注。

(3)drawSprite

void drawSprite(const SkBitmap& bitmap, int x, int y, const SkPaint* paint);

无视SkCanvas的矩阵状态,将bitmap平移到x,y的位置。

(4)drawBitmapMatrix

void drawBitmapMatrix(const SkBitmap& bitmap, const SkMatrix& matrix, const SkPaint* paint);

绘制的bitmap带有matrix的矩形变换,需要和SkCanvas的矩形变换叠加。

(5)drawRect

void drawRect(const SkRect& r, const SkPaint& paint);

这个是最通用的方法,多用于需要加入额外效果的场景,比如需要绘制重复纹理。关于Tile的两个参数就是OpenGL纹理贴图中水平垂直方向上的边界处理模式。

由这种用法,大家不难类推到非矩形图像绘制的方法,比如画圆角矩形图标、把方图片裁剪成一个圆等。

下面是一个Demo程序

<span style="font-size:14px;">#include "SkBitmapProcShader.h"
#include "SkCanvas.h"
#include "SkBitmap.h"
#include "SkImageDecoder.h"
#include "SkImageEncoder.h"
#include "SkRect.h"
int main()
{
const int w = 1080;
const int h = 1920;
/*准备目标图片和源图片*/
SkBitmap dst;
dst.allocPixels(SkImageInfo::Make(w, h, kN32_SkColorType, kPremul_SkAlphaType));
SkCanvas c(dst);

SkBitmap src;
SkImageDecoder::DecodeFile("test.jpg", &src);

/*各种绘制图片方法使用示例*/
{
c.drawBitmap(src, 0, 0, NULL);
}

{
c.drawSprite(src, 400, 400, NULL);
}
{
SkRect dstR;
r.set(29, 29, 100, 100);
SkRect srcR;
r.set(0,0,40,50);
c.drawBitmapRectToRect(src, &srcR, dstR, NULL);
}
{
SkMatrix m;
m.setScale(1.4,4.3);
c.drawBitmapMatrix(src, m, NULL);
}
{
SkRect dstRect;
dstRect.set(100,100,480,920);
SkPaint paint;
SkMatrix m;
m.setScale(3.2, 4.1);
SkShader* shader = CreateBitmapShader(src, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, m, NULL);
paint.setShader(shader);
SkSafeUnref(shader);
c.drawRect(dstRect, paint);
}

/*输出图片*/
SkImageEncoder::EncodeFile("output.jpg", dst, SkImageEncoder::kJPEG_Type, 100);
return 1;
}</span>


2、流程解析



(1)SkCanvas两重循环调到SkBitmapDevice,进而调到SkDraw

在SkDraw中,drawBitmap的渲染函数统一为:

void SkDraw::drawBitmap(const SkBitmap& bitmap, const SkMatrix& prematrix, const SkPaint& origPaint) const;

(2)Sprite简易模式

在满足如下条件时,走进Sprite简易模式。

代码见 external/skia/src/core/SkDraw.cpp drawBitmap 函数

a、(bitmap.colorType() != kAlpha_8_SkColorType && just_translate(matrix, bitmap))

kAlpha_8_SkColorType 的图像只有一个通道alpha,按 drawMask 方式处理,将Paint中的颜色按图像的alpha预乘,叠加到目标区域上。

just_translate表示matrix为一个平移矩阵,这时不涉及旋转缩放,bitmap的像素点和SkCanvas绑定的dstBitmap的像素点此时存在连续的一一对齐关系。

b、clipHandlesSprite(*fRC, ix, iy, bitmap))

这个条件是指当前SkCanvas的裁剪区域不需要考虑抗锯齿或者完全包含了bitmap的渲染区域。SkCanvas的任何渲染都必须在裁剪区域之内,因此如果图像跨越了裁剪区域边界而且裁剪区域需要考虑抗锯齿,在边界上需要做特殊处理。

注:裁剪区域的设置API

void SkCanvas::clipRect(const SkRect& rect, SkRegion::Op op, bool doAA)

doAA即是否在r的边界非整数时考虑抗锯齿。

满足条件,创建SkSpriteBlitter,由SkScan::FillIRect按每个裁剪区域调用SkSpriteBlitter的blitRect。

这种情况下可以直接做颜色转换和透明度合成渲染过去,不需要做抗锯齿和图像插值,也就不需要走取样——混合流程,性能是最高的。

满足条件后通过ChooseSprite去选一个SkSpriteBlitter

详细代码见 external/skia/src/core/SkBlitter_Sprite.cpp 中的 ChooseSprite 函数。

这函数实际上很多场景都没覆盖到,因此很可能是选不到的,这时就开始转回drawRect流程。

(3)创建BitmapShader

在 SkAutoBitmapShaderInstall install(bitmap, paint); 这一句代码中,为paint创建了bitmapShader:

fPaint.setShader(CreateBitmapShader(src, SkShader::kClamp_TileMode,

SkShader::kClamp_TileMode,

localMatrix, &fAllocator));

然后就可以使用drawRect画图像了。

(4)drawRect

不能使用SkSpriteBlitter的场景,走drawRect通用流程。

这里有非常多的分支,只讲绘制实矩形的情形。

通过 SkAutoBlitterChoose -> SkBlitter::Choose,根据Canvas绑定的Bitmap像素模式,paint属性去选择blitter。

绘制图片时paint有Shader(SkBitmapProcShader),因此是选的是带Shader的Blitter,比如适应ARGB格式的 SkARGB32_Shader_Blitter

(5)SkScan

在SkScan中,对每一个裁剪区域,将其与绘制的rect求交,然后渲染这个相交区域。此外,在需要时做抗锯齿。

做抗锯齿的基本方法就是对浮点的坐标,按其离整数的偏离度给一个alpha权重,将颜色乘以此权重(减淡颜色)画上去。

SkScan中在绘制矩形时,先用blitV绘制左右边界,再用blitAntiH绘制上下边界,中间大块的不需要考虑抗锯齿,因而用blitRect。

(6)blitRect

这一步先通过 Shader的shadeSpan方法取对应位置的像素,再将此像素通过SkBlitRow的proc叠加上去。

如果不需要考虑混合模式,可以跳过proc。

参考代码:external/skia/src/core/SkBlitter_ARGB32.cpp 中的blitRect

(7)shadeSpan

这里只考虑 SkBitmapProcShader 的shadeSpan,这主要是图像采样的方法。详细代码见 external/skia/src/core/SkBitmapProcShader.cpp

对每一个目标点,先通过 matrixProc 取出需要参考的源图像素,然后用sampleProc将这些像素合成为一个像素值。(和OpenGL里面的texture2D函数原理很类似)。

若存在 shaderProc(做线性插值时,上面的步骤是可以优化的,完全可以取出一群像素一起做插值计算),以shaderProc代替上面的两步流程,起性能优化作用。

3、SkBlitter接口解析

(1)blitH

virtual void blitH(int x, int y, int width);

从x,y坐标开始,渲染一行width个像素

(2)blitV

virtual void blitV(int x, int y, int height, SkAlpha alpha);

从x,y开始,渲染一列height个像素,按alpha值对颜色做减淡处理

(3)blitAntiH

virtual void blitAntiH(int x, int y, const SkAlpha antialias[], const int16_t runs[]);

如流程图所标示的,这个函数的用来渲染上下边界,作抗锯齿处理。

(4)blitRect

virtual void blitRect(int x, int y, int width, int height);

绘制矩形区域,这个地方就不需要考虑任何的几何变换、抗锯齿等因素了。

(5)blitMask

virtual void blitMask(const SkMask& mask, const SkIRect& clip);

主要绘制文字时使用,以一个颜色乘上mash中的透明度,叠加。

版权声明:本文为博主原创文章,未经博主允许不得转载。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android skia 图形图像