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 的效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liulun

如果文章真帮到了你,谢谢您打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值