当前位置: 首页 > news >正文

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

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

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

版权声明:本文为博主原创文章,未经博主允许不得转载。

转载于:https://my.oschina.net/jxt1234and2010/blog/517727

相关文章:

  • 高富帅与大公司 续三 自我认知
  • Asp.net安全架构之3:CSRF(跨站点请求伪造)
  • flat UI
  • 监控中心机房设计方案
  • 我的Android进阶之旅------【强力推荐】Android开源图表库XCL-Charts版本发布及展示页...
  • 10月13日全球域名商(国际域名)解析新增量TOP20
  • SQL网站注入步骤
  • 让ShearPoint 2010在线广播PPT演示文稿
  • 基于android混合开发的JsBridge技术学习
  • coredata理解
  • 天轰穿结束了,结束了浮躁的生活
  • cisco路由器分时段端口限速
  • visualC/C++连接MySql数据库
  • 【转载】Oracle10g数据类型总结
  • spark-submit工具参数说明
  • 收藏网友的 源程序下载网
  • 2017 前端面试准备 - 收藏集 - 掘金
  • 77. Combinations
  • create-react-app项目添加less配置
  • CSS居中完全指南——构建CSS居中决策树
  • Effective Java 笔记(一)
  • Invalidate和postInvalidate的区别
  • Java Agent 学习笔记
  • JavaScript函数式编程(一)
  • Java深入 - 深入理解Java集合
  • Js基础——数据类型之Null和Undefined
  • nginx 负载服务器优化
  • PHP 小技巧
  • Redis学习笔记 - pipline(流水线、管道)
  • 简单基于spring的redis配置(单机和集群模式)
  • 数组大概知多少
  • 说说动画卡顿的解决方案
  • 微信小程序设置上一页数据
  • 为视图添加丝滑的水波纹
  • 小程序 setData 学问多
  • 携程小程序初体验
  • 用Node EJS写一个爬虫脚本每天定时给心爱的她发一封暖心邮件
  • 与 ConTeXt MkIV 官方文档的接驳
  • 原生JS动态加载JS、CSS文件及代码脚本
  • d²y/dx²; 偏导数问题 请问f1 f2是什么意思
  • ​secrets --- 生成管理密码的安全随机数​
  • #QT(一种朴素的计算器实现方法)
  • #我与Java虚拟机的故事#连载16:打开Java世界大门的钥匙
  • $分析了六十多年间100万字的政府工作报告,我看到了这样的变迁
  • (2)MFC+openGL单文档框架glFrame
  • (二)正点原子I.MX6ULL u-boot移植
  • (附源码)spring boot基于Java的电影院售票与管理系统毕业设计 011449
  • (附源码)spring boot校园拼车微信小程序 毕业设计 091617
  • (附源码)ssm户外用品商城 毕业设计 112346
  • (转)利用ant在Mac 下自动化打包签名Android程序
  • .NET “底层”异步编程模式——异步编程模型(Asynchronous Programming Model,APM)...
  • .net core 源码_ASP.NET Core之Identity源码学习
  • /bin/bash^M: bad interpreter: No such file or directory
  • /dev下添加设备节点的方法步骤(通过device_create)
  • /etc/fstab和/etc/mtab的区别