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

SkSurface---像素的容器:表面

如果说 SkCanvas 是画布,是所有绘图操作的提供者的话,那么 SkSurface 就是画布的容器,我们称之为表面,它负责管理画布对应的像素数据。这些像素数据可以是在内存中创建的,也可以是在 GPU 显存中创建的。

创建一个空白表面

如何创建和使用 Skia 的 SkSurface 对象,如下代码所示:

SkImageInfo imgInfo = SkImageInfo::MakeN32Premul(800, 600);
sk_sp<SkSurface> surface = SkSurfaces::Raster(imgInfo);

SkSurfaces::Raster 方法执行后,应用程序将在内存中分配指定数量的像素。

若需要使用 SkSurface 对象管理的像素,可以通过如下方法来完成工作:

SkPixmap pixmap;
surface->peekPixels(&pixmap);
auto addr = pixmap.addr();

pixmap.addr() 用于获取像素数据的内存地址,一般情况下,开发者不会使用这个地址来改变具体的某个像素的值。

因为内存中存储的像素数据是被格式化过的,并不是A,R,G,B颜色分量依次排列的数值。像素数据在内存中的排布方式与 SkSurface 的颜色空间有关。

通过如下代码,你可以获取某个具体位置的颜色:

SkColor color = pixmap.getColor(0, 0);  //画布上位置 0,0 的颜色

根据现有像素数据创建表面

如果你已经拥有了像素数据,就可以直接基于你的像素数据创建 SkSurface 对象,如下代码所示:

std::vector<SkColor> surfaceMemory;
surfaceMemory.resize(w * h);
SkImageInfo info = SkImageInfo::MakeN32Premul(w, h);
sk_sp<SkSurface> surface = SkSurfaces::WrapPixels(info, &surfaceMemory.front(), w * sizeof(SkColor));

上面代码中使用 std::vector<SkColor> 来存储像素数据。

每个像素就是一个 SkColor 类型的数据(SkColor就是 uint32_t )。

像素容器的大小是 w * h,(二维数组的列数 w 和行数 h ,与窗口的宽和高相同)

SkSurfaces::WrapPixels 方法基于像素数据创建 SkSurface 对象。

其中 &surfaceMemory.front() 用于获取容器内第一个像素的地址,

w * sizeof(SkColor) 是每行数据的大小。

同样道理,如果你有一个 SkBitmap 对象,就可以根据这个对象的像素数据创建表面:

SkBitmap bitmap;
bitmap.allocN32Pixels(w, h);
sk_sp<SkSurface> surface = SkSurfaces::WrapPixels(bitmap.pixmap());

上述代码使用 SkBitmap 对象的 allocN32Pixels 方法为此对象创建了指定大小的内存空间用于存储像素数据。

SkBitmap 对象的 pixmap 方法负责获取 SkBitmap 对象的像素数据的内存地址。

重置表面像素数据

如果你想批量把一个表面的像素数据全部设置为某个颜色值。

就可以用以下两个办法达到这个目的:

auto canvas = surface->getCanvas();
canvas->clear(0x66778899);  //方法1
canvas->drawColor(0x22FF8899); //方法2
SkPaint paintObj;
canvas->drawPaint(paintObj); //方法3

这都是通过画布对象 SkCanvas 完成的。

也可以在源头设置所有像素的值,如下代码所示

std::vector<SkColor> surfaceMemory(w * h, 0xffff00); //初始化像素数组时,即设置好像素颜色surfaceMemory.resize(w * h,0xffff00); //更改像素容器大小时,重置整个容器的像素颜色

覆盖表面像素数据

如果你已经有了一组像素数据,需要把这些像素写入目标表面,可以通过如下方法完成:

void surfaceWritePixels(SkSurface *surface)
{std::vector<SkColor> srcMem(200 * 200, 0xff00ffff);SkBitmap dstBitmap;dstBitmap.setInfo(SkImageInfo::MakeN32Premul(200, 200));dstBitmap.setPixels(&srcMem.front());surface->writePixels(dstBitmap, 100, 100);
}

在这段代码中,使用 SkBitmap 类型包装了像素数据。

SkBitmap 对象的 setPixels 方法会把 srcMem 管理的像素地址拷贝到 SkBitmap 对象中(只复制了地址)

SkSurface 对象的 writePixels 方法会把 dstBitmap 对象管理的像素数据复制到自己管理的内存数据中。

复制是从 100,100 的位置开始的,由于 dstBitmap 只管理了 200×200 的像素数据,所以复制工作执行到坐标 300,300 就结束了。

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

覆盖表面像素.png

表面与画布的互访

通过 SkSurface 对象的 getCanvas 方法得到一个与此表面有关的画布。

也可以通过 SkCanvas 对象的 getSurface 方法得到一个与此画布有关的表面。

但需要注意的是,SkCanvas 对象的 getSurface 方法并不是总能得到与之对应的 SkSurface 对象。

比如:

auto canvas = SkCanvas::MakeRasterDirect(info, &surfaceMemory.front(), 4 * w);
auto surface = canvas->getSurface();

这个 canvas 对象是手动创建的,它不依赖任何surface,它的 getSurface 方法的返回值就是一个空指针。

虽然这个 canvas 没有与之对应的 surface,可以通过如下方法创建一个:

SkImageInfo info = SkImageInfo::MakeN32Premul(w, h);
auto surface = canvas->makeSurface(info);

需要注意的是,这样做会导致 canvas 对应的像素数据被复制到 surface 内(内存占用加倍)

所以要么先创建surface再通过surface得到canvas,要么仅使用canvas,不使用surface

尽量不要通过canvas去创建surface

当一个应用中存在多个surface的时候,往往需要合并这些surface

有很多办法可以完成这项任务,其中之一就是把一个 surface 绘制到另一个 canvas 中,如下代码所示:

surface->draw(canvas.get(), 0, 0);

这行代码会把 surface 管理的像素数据,绘制到 canvas 画布上(从坐标 0,0 位置开始绘制),这就达到了合并两个 surface 的效果。

http://www.lryc.cn/news/603340.html

相关文章:

  • echarts饼图
  • .NET测试平台Parasoft dotTEST在汽车电子行业的核心功能及应用
  • OpenAI Python API 完全指南:从入门到实战
  • 使用jQuery动态操作HTML和CSS
  • 从centos更换至ubuntu的安装、配置、操作记录
  • 系统选择菜单(ubuntu grub)介绍
  • 智能健康项链专利拆解:ECG 与 TBI 双模态监测的硬件架构与信号融合
  • Ubuntu22.04系统安装,Nvidia显卡驱动安装问题
  • 【Linux系统编程】Ext2文件系统
  • Java 9 新特性解析
  • VR全景制作流程分享-众趣VR全景制作平台
  • 博物馆 VR 导览:图形渲染算法+智能讲解技术算法实现及优化
  • 以需求破局:DPVR AI Glasses 重塑 AI 眼镜产业生态
  • 【OpenAI】ChatGPT辅助编码:Spring Boot + Copilot自动生成业务逻辑
  • Agent常用搜索引擎Tavily使用学习
  • VR 三维重建:开启沉浸式体验新时代
  • idea 服务器Debug端口启动设置
  • 深度学习 目标检测常见指标和yolov1分析
  • Vue 3 响应式变量笔记
  • Chrome 提示 “此扩展程序不再受支持”(MacOS/Windows)
  • RabbitMQ面试精讲 Day 6:消息确认与事务机制
  • STL学习(?常用的遍历算法和查找算法)
  • 从协议栈到ath12k_mac_op_tx的完整调用路径
  • 云原生MySQL Operator开发实战(五):扩展与生态系统集成
  • Python 程序设计讲义(28):字符串的用法——格式化字符串
  • go install报错: should be v0 or v1, not v2问题解决
  • Vulkan入门教程 | 第二部分:创建实例
  • Docker用Web应用实例深入容器
  • Go语言实战案例-判断二叉树是否对称
  • 本地安装 SQLite 的详细步骤