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

此篇讲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中的透明度,叠加。

时间: 2024-08-04 06:56:20

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

Skia深入分析1——skia上下文

前言:         断断续续跟Android的skia库打了两年交道,如今交接掉了,便写写关于skia的一些知识,也算了结一段职业生涯. 找了找网上关于skia的文章,基本上都过时了,讲得也不怎么深入.虽然Skia只是一个2D引擎,但其深度优化的算法.完善的渲染体系和精炼的代码框架,还是很值得借鉴的.         PS:文章所依据的代码为目前最新的Android 5.0.2. 基本章节规划如下: 1.Skia的上下文(即本章) 2.Skia基本渲染流程与框架 3.Skia图像绘制分析 4

Skia深入分析6——skia中图像编解码代码概述

1.API和自注册机制 Skia中编码解码图片都只需要一行代码: SkBitmap bitmap; SkImageDecoder::DecodeFile("test.xxx", &bitmap);//由文件名解码,自动推断图片类型 //或者由流解码 SkFILEStream stream("test.xxx"); SkImageDecoder::DecodeStream(stream, &bitmap);//由输入流解码,自动推断图片类型 //编码

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

此篇讲图像采样一.采样流程在上一节里的流程图有写到,图像绘制的实际渲染发生在某个blitter的blitRect函数中,我们先看一个具体的blitRect实现. void SkARGB32_Shader_Blitter::blitRect(int x, int y, int width, int height) { SkASSERT(x >= 0 && y >= 0 && x + width <= fDevice.width() && y

Skia深入分析5——skia文字绘制的实现

文字绘制主要包括编码转换(主要是中文).字形解析(点线或image)和实际渲染三个步骤.在这个过程中,字形解析和实际渲染均是耗时步骤.Skia对文字解析的结果做了一套缓存机制.在中文字较多,使用多种字体,绘制的样式(粗/斜体)有变化时,这个缓存会变得很大,因此Skia文字缓存做了内存上的限制. 1.SkPaint 文字绘制与SkPaint的属性相关很大,先回头看下SkPaint相关的属性 class SkPaint { private SkTypeface* fTypeface;//字体 SkP

Skia深入分析4——skia路径绘制的实现

Skia路径绘制代码分析 路径绘制尽管使用频率相对于图像绘制.文本绘制低,但却是非常重要的一个基本特性.所有不规则图形(椭圆.圆角矩形.三角形.简单的文字),最后都避不开路径绘制. 而且,若自己实现一个2D引擎,这块内容是很具有参考意义的,用OpenGL的话,图像采样等都很少关注了,对对坐标就好.但菱角.圆弧.曲线等如何绘制仍然是一个难题,这时就可以参考Skia中drawPath的实现. 由于涉及较多的图形学知识,本章就不讲相关公式了,只讲讲基本的流程.一.SkPath类 在之前的图像绘制并没有

Skia深入分析8——Skia的GPU绘图

Skia的GPU绘图 一.Skia-GPU概述 在Android4.2到Android5.0的过程中,skia中开发较频繁的部分莫过于GPU加速部分和延迟渲染机制,尽管目前来看几乎没有用到,但后续很可能会在Frameworks层引入. 在Android上面,只可能使用OpenGL,因此作为使用OpenGL的绘图引擎,关注如下要点即可: 1.OpenGL上下文如何建立(关系到如何显示绘制结果) 2.顶点如何生成 3.着色器如何管理,特效怎么设置 4.纹理.vbo.字体cache等缓存管理机制 由于

Skia深入分析10——Skia库的性能与优化潜力

Skia库性能与优化潜力 图形/渲染 算法/架构 作为图形渲染引擎,性能上是非常重要的,按通常Android手机60帧的刷新率,绘制一帧的总时间只有16ms,可谓是毫厘必争.提升性能到最后,就必然跟不同CPU的特性打交道,毕竟一个SIMD下去,好做的提升5.6倍,不那么好做的也达到2.3倍,收益极其可观. SIMD,在intel上是SSE,在arm上是neon,在mips上则是其dsp功能.使用SIMD,需要代码架构是满足内存连续性要求的,否则需要重构,Skia作为正常的图形渲染引擎,采用行渲染

Skia深入分析2——skia渲染架构

一.渲染层级从渲染流程上分,Skia可分为如下三个层级: 1.指令层:SkPicture.SkDeferredCanvas->SkCanvas 这一层决定需要执行哪些绘图操作,绘图操作的预变换矩阵,当前裁剪区域,绘图操作产生在哪些layer上,Layer的生成与合并. 2.解析层:SkBitmapDevice->SkDraw->SkScan.SkDraw1Glyph::Proc 这一层决定绘制方式,完成坐标变换,解析出需要绘制的形体(点/线/规整矩形)并做好抗锯齿处理,进行相关资源解析并

Skia深入分析7——区域解码

Skia深入分析7--区域解码 1.概述 -当图片很大时,解码速度缓慢,占用内存很高,并且,当图片超过一定尺寸时,无法做纹理上传和显示(这跟GPU能力有关,一般的GPU是8192*8192).这时只好做下采样,但会牺牲图片显示的质量. -对于图库等需要清晰浏览图片的应用,不可能设置一个下采样率去解决这一问题,因此,Google加入了区域解码这个功能,使我们可以从原始的图片文件中,解出一部分区域完整的图片内容. -区域解码的难点主要在于定位像素区域所对应的文件位置,这个需要图像编码时有一定的连续性