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

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

创建一个空白表面

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

cpp 复制代码
SkImageInfo imgInfo = SkImageInfo::MakeN32Premul(800, 600);
sk_sp<SkSurface> surface = SkSurfaces::Raster(imgInfo);

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

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

cpp 复制代码
SkPixmap pixmap;
surface->peekPixels(&pixmap);
auto addr = pixmap.addr();

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

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

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

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

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

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

cpp 复制代码
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 对象,就可以根据这个对象的像素数据创建表面:

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

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

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

重置表面像素数据

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

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

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

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

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

cpp 复制代码
std::vector<SkColor> surfaceMemory(w * h, 0xffff00); //初始化像素数组时,即设置好像素颜色

surfaceMemory.resize(w * h,0xffff00); //更改像素容器大小时,重置整个容器的像素颜色

覆盖表面像素数据

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

cpp 复制代码
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 就结束了。

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

表面与画布的互访

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

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

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

比如:

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

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

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

cpp 复制代码
SkImageInfo info = SkImageInfo::MakeN32Premul(w, h);
auto surface = canvas->makeSurface(info);

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

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

尽量不要通过canvas去创建surface

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

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

cpp 复制代码
surface->draw(canvas.get(), 0, 0);

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

相关推荐
ACP广源盛139246256732 小时前
IX8024与科学大模型的碰撞@ACP#筑牢科研 AI 算力高速枢纽分享
运维·服务器·网络·数据库·人工智能·嵌入式硬件·电脑
Empty-Filled2 小时前
AI生成测试用例功能怎么测:一个完整实战案例
网络·人工智能·测试用例
码云数智-大飞3 小时前
本地部署大模型:隐私安全与多元优势一站式解读
运维·网络·人工智能
jinanwuhuaguo3 小时前
(第二十九篇)OpenClaw 实时与具身的跃迁——从异步孤岛到数字世界的“原住民”
前端·网络·人工智能·重构·openclaw
汤愈韬4 小时前
三种常用 NAT 的经典案例
网络协议·网络安全·security
等风来不如迎风去4 小时前
【win11】最佳性能:fix 没有壁纸,一直黑屏
网络·人工智能
Harvy_没救了4 小时前
【网络部署】 Win11 + VMware CentOS8 + Nginx 文件共享服务 Wiki
运维·网络·nginx
汤愈韬5 小时前
NAT Server 与目的Nat
网络·网络协议·网络安全·security
2401_873479405 小时前
断网时如何实时判断IP归属?嵌入本地离线库,保障风控不中断
运维·服务器·网络
7ACE6 小时前
Wireshark TS | TLP 超时时间
网络·网络协议·tcp/ip·wireshark·tcpdump