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

Skia 的核心类---深入画布SkCanvas

SkCanvas 是 Skia 的核心类之一,我们可以把它看作画布,它提供了用于绘制图形的接口。

移动画布

有一些特殊的时候,我们需要按元素相对另一个元素的定位来绘制元素(比如:子元素相对父元素的位置进行定位)

Skia 本身并不提供元素的父子关系管理,布局管理是开发者自己的事儿。

在这些时候,我们往往希望移动画布的位置,而不是把元素的相对坐标转换为绝对坐标。

Skia 为我们提供了移动画布的工具方法,如下代码所示:

// #include "include/core/SkCanvas.h"void translateCanvas(SkCanvas* canvas) {auto rect = SkRect::MakeXYWH(10, 10, 80, 80);SkPaint paint;paint.setColor(0xff00ffff);canvas->drawRect(rect, paint);canvas->translate(90, 90);paint.setColor(0xffff00ff);canvas->drawRect(rect, paint);canvas->translate(90, 90);paint.setColor(0xffffff00);canvas->drawRect(rect, paint);
}

SkCanvas 对象的 translate 方法负责移动画布,

它的两个参数分别是画布在 x方向 和在 y方向上 移动的距离。

在这段代码中,我们第一次绘制矩形时,画布的位置并没有移动,矩形被绘制在 10,10 的位置处。

第二次绘制矩形时,画布的位置被向右、向下分别移动了 90 像素,

所以,即使没有改变矩形的 x,y 坐标,最终矩形被绘制在 100,100 处(90+10)

紧接着,把画布 在现有的位置的基础上 向右、向下又分别移动了90像素,

所以,第三次绘制的矩形实际坐标应该是 200,200(90+10+90+10)。

程序最终运行的结果如下图所示:

移动画布.png

旋转画布

除了在垂直和水平方向移动画布之外,Skia 还支持旋转画布,如下代码所示:

void rotateCanvas(SkCanvas* canvas) {auto rect = SkRect::MakeXYWH(w/2-50, h/2-100, 100, 200);SkPaint paint;paint.setColor(0xff00ffff);canvas->drawRect(rect, paint);canvas->rotate(45, w / 2, h / 2);paint.setColor(0xffffff00);canvas->drawRect(rect, paint);
}

在这段代码中,我们首先在窗口的中心画了一个蓝色矩形,

然后让画布绕着窗口中心旋转了 45 度。

SkCanvas 对象的 rotate 方法的第一个参数为 旋转角度(正数顺时针,负数为逆时针),后两个参数是旋转中心坐标

画布旋转完成后我们又用黄色笔刷绘制了一遍这个矩形。

最终程序运行结果如下:

旋转画布.png

缩放画布

除了移动和旋转画布外,Skia还支持画布缩放,如下代码所示:

void scaleCanvas(SkCanvas* canvas) {auto rect = SkRect::MakeXYWH(10, 10, 100, 100);SkPaint paint;paint.setColor(0xff00ffff);canvas->drawRect(rect, paint);canvas->scale(2, 2);paint.setColor(0xffffff00);canvas->drawRect(rect, paint);
}

 

这段代码我们使用 SkCanvas 对象的 scale 方法把画布放大了两倍(分别从x轴方向和y轴方向都放大了两倍),

示例中先绘制的矩形(蓝色)大小为 100*100 ,画布放大后,绘制同样的矩形(黄色)大小即变成了 200*200

值得注意的是随着画布的放大,画布的坐标体系也跟着放大了,黄色矩形的位置也从 10,10 变成了 20,20

最终程序运行结果如下图所示:

缩放画布.png

画布矩阵变换

如果想一次性为画布应用多种变换状态,那么你可以使用 SkCanvas 的矩阵变换的能力,如下代码所示:

void matrixCanvas(SkCanvas* canvas) {SkMatrix matrix;matrix.postTranslate(50, 50); //移动matrix.postRotate(8);  //顺时针旋转 8 度matrix.postScale(1.5, 1.5); //放大canvas->concat(matrix);auto rect = SkRect::MakeXYWH(0, 0, 100, 100);SkPaint paint;paint.setAntiAlias(true);paint.setColor(0xff00ffff);canvas->drawRect(rect, paint);
}

此示例中用到了一个新类型 SkMatrix (矩阵),它用于处理各种类型的二维变换,包括平移、旋转、缩放和倾斜等。

本示例中使用一个 SkMatrix 对象记录了多种变换(移动、旋转和放大),最后一次性的把这多种变换应用到画布上(这个工作是通过 SkCanvas 对象的 concat 方法完成的)。

最终程序运行结果如下图所示:

画布矩阵变换.png

倾斜画布与保存画布状态

移动、旋转和缩放画布都是画布的基本能力,除了这些能力之外,Skia 的画布还具备倾斜能力,此能力常用于绘制元素的阴影效果。

示例代码如下所示:

void skewCanvas(SkCanvas* canvas) {auto rect = SkRect::MakeXYWH(60, 60, 80, 80);SkPaint paint;paint.setAntiAlias(true);paint.setColor(0xff00ffff);canvas->drawRect(rect, paint);canvas->save();canvas->translate(60, 140);canvas->skew(1, 0);paint.setColor(0xffff00ff); //粉色paint.setStroke(false);rect = SkRect::MakeXYWH(0, 0, 80, 80);canvas->drawRect(rect, paint);canvas->restore();canvas->translate(140, 60);canvas->skew(0, 1);paint.setColor(0xffffff00); //黄色paint.setStroke(false);rect = SkRect::MakeXYWH(0, 0, 80, 80);canvas->drawRect(rect, paint);
}

这段代码的运行结果如下图所示:

倾斜画布.png

这段代码有以下三点需要注意:

  1. 保存与恢复画布状态

    无论是移动画布,旋转画布还是倾斜画布,我们都希望 在改变画布状态之后再恢复画布的状态

    这时就需要用到 SkCanvas 的 save 方法和 restore 方法。

    save 方法用于保存画布的当前状态,restore 方法用于把画布恢复到之前的状态。

    在上面示例代码中,调用 SkCanvas 的 save 方法时,画布还没有被移动和拉伸,是原始状态。

    之后的代码对画布执行了移动和拉伸操作。

    直到调用 SkCanvas 的 restore 方法后,画布状态就恢复到了原始状态。

    在此之后无论执行 translate 方法还是 skew 方法,都是在一个原始状态的画布上执行的。

    如果不执行 restore 方法,那么对画布的移动和拉伸操作就是在现有画布状态上执行的。

  2. 拉伸画布

    示例结果看起来像立方体,其蓝色面是一个正常的矩形,粉色面和黄色面是画布在两次倾斜后绘制的内容。

    SkCanvas 的 skew 方法负责倾斜画布,这个方法接收两个参数,

    第一个参数是画布在 x 轴上的倾斜的大小,第二个参数是画布在 y 轴上的倾斜的大小

    值得注意的是,在倾斜画布之前,需要先移动画布,以确定画布在哪个点开始倾斜

    以粉色面来看,画布从正方形的左下角(坐标:60, 140 )开始,在 x 轴上倾斜了 1。

    以黄色面来看,画布从正方形的右上角(坐标:140, 60 )开始,在 y 轴上倾斜了 1。

    画布倾斜之后,再在画布上绘制原有的矩形(正方形),矩形就变成了平行四边形了。

  3. 多次保存画布状态与恢复

    实际应用中,可能会连续保存多次画布状态,然后再根据程序的运行情况确定恢复到哪一次保存的画布状态。

    类似这种需求,你可以通过如下代码实现:

auto count = canvas->getSaveCount(); // count == 1
canvas->save();
//...
canvas->save();
//...
count = canvas->getSaveCount(); //count == 3
canvas->restoreToCount(0);  //恢复到最初的状态
count = canvas->getSaveCount(); //count == 1

代码中 getSaveCount 方法用于获取当前画布持有的状态的数量。restoreToCount方法用于恢复到指定的画布状态。

保存画布图层

除了上面介绍的保存画布状态之外,还可以把画布状态保存到一个 画布图层 中,

同时还可以为这个 画布图层 设置一个 SkPaint 对象,来控制接下来绘制工作的效果。

如下代码所示:

void saveCanvasLayer(SkCanvas* canvas) {auto rect = SkRect::MakeXYWH(20, 20, 100, 100);SkPaint paint;paint.setAntiAlias(true);paint.setColor(0xff00ffff);canvas->drawRect(rect, paint);SkRect bounds = SkRect::MakeLTRB(0, 0, 160, 160);SkPaint layerPaint;layerPaint.setAlphaf(0.5f);canvas->saveLayer(&bounds, &layerPaint);rect = SkRect::MakeXYWH(80, 80, 200, 200);paint.setColor(0xffff00ff);canvas->drawRect(rect, paint);canvas->restore();
}

这段代码的运行结果如下图所示:

保存画布图层.png

在这段代码中先绘制了一个蓝色矩形作为参照,

接着使用 SkCanvas 对象的 saveLayer 方法保存了画布状态。

这个方法的第一个参数是指定的区域,第二个参数是一个 SkPaint 对象。

可以简单的认为 saveLayer 方法执行完之后,画布的绘图区域变成 bounds 指定的区域。

所有超出这个区域的内容都不会被绘制到最终的画布上。

restore 方法除了恢复画布状态之外,还负责把 saveLayer 之后绘制的内容绘制到最终的画布上。

如果不执行 restore 方法,最终画布上将不会出现那个紫色矩形的。

另外,紫色矩形宽高都是 200 ,明显大于蓝色矩形,但实际结果却小于蓝色矩形,这是因为紫色矩形只有左上角一小部分被绘制到最终画布上了。

值得注意的是,绘制这些内容到最终画布上时,使用了 SkPaint 对象,也就是说,所有内容都是以半透明的方式绘制到最终画布上的。

这非常有用,因为 SkPaint 对象可以携带很多效果,比如各种 filter 和 shader ,设置了这个 paint 对象之后,在 layer 上绘制的任何内容都会附加这个 paint 对象携带的效果。

http://www.xdnf.cn/news/15640.html

相关文章:

  • Jfinal+SQLite处理 sqlite数据库执行FIND_IN_SET报错
  • Spring AI:程序调用 AI 大模型
  • Python编程进阶知识之第二课学习网络爬虫(selenium)
  • Java HashMap key为Integer时,遍历是有序还是无序?
  • 信息学奥赛一本通 1575:【例 1】二叉苹果树 | 洛谷 P2015 二叉苹果树
  • 基于LiteNetLib的Server/Client Demo
  • 深入理解 Redis 集群化看门狗机制:原理、实践与风险
  • 当OT遇见IT:Apache IoTDB如何用“时序空间一体化“技术破解工业物联网数据孤岛困局?
  • iOS 文件深度调试实战 查看用户文件 App 沙盒 系统文件与日志全指南
  • iOS WebView 调试实战 全流程排查接口异常 请求丢失与跨域问题
  • 深入理解进程地址空间:虚拟内存与进程独立性
  • 首个直播流扩散(LSD)AI模型:MirageLSD,它可以实时把任意视频流转换成你的自定义服装风格——虚拟换装新体验
  • LVS(Linux Virtual Server)详细笔记(实战篇)
  • 基于ROS2进行相机标定,并通过测试相机到棋盘格之间的距离进行验证
  • SpringSecurity-spring security单点登录
  • 【数据结构初阶】--双向链表(一)
  • VUE目录结构详解
  • 1 初识C++
  • ElasticSearch Doc Values和Fielddata详解
  • 数学积分方程显式求解
  • Android性能优化之电量优化
  • http与https的主要区别是什么?
  • http性能测试命令ab
  • sqli-labs靶场通关笔记:第29-31关 HTTP参数污染
  • 【前端】输入框输入内容时,根据文本长度自动分割,中间用横杠分割
  • 模版匹配的曲线好看与否有影响吗?
  • Git 中如何比较不同版本之间的差异?常用命令有哪些?
  • 金属伪影校正的双域联合深度学习框架复现
  • Prometheus错误率监控与告警实战:如何自定义规则精准预警服务器异常
  • Spring Boot 应用优雅停机与资源清理:深入理解关闭钩子