调用Skia内置位图编解码器的坑点

Skia内置了对大部分图片格式的编解码支持,几乎所有格式都可以解码,编码主要支持:JPEG,PNG,WEBP等。然而想直接调用Skia去编解码图片,还有

Skia内置了对大部分图片格式的编解码支持,几乎所有格式都可以解码,编码主要支持:JPEG,PNG,WEBP等。然而想直接调用Skia去编解码图片,还有一些注意事项,这方面几乎没搜索到多少相关文档,一开始走了不少弯路,通过研究源码才一点一点走通了,这里总结一下方便遇到同样问题的人。

先来看最常用的位图解码:

bool decode(const void* bytes, size_t length, SkBitmap* image) {

sk_sp<SkData> data = SkData::MakeWithoutCopy(bytes, length);

SkAutoTDelete<SkCodec> codec(SkCodec::NewFromData(data.get()));

if (!codec) {

return false;

}

SkImageInfo info = codec->getInfo();

auto alphaType = info.alphaType();

auto transparent = (alphaType != kOpaque_SkAlphaType);

if (transparent && alphaType != kPremul_SkAlphaType) {

// Skia requires premultiplied alpha data.

info = info.makeAlphaType(kPremul_SkAlphaType);

}

auto result = codec->getPixels(info, image->getPixels(), image->rowBytes());

return result == SkCodec::kSuccess;

}

上面这个函数就能实现调用Skia对字节流数据进行位图解码。解释一下几个参数: bytes是位图文件的字节流首地址,这个很简单,通过各种标准文件系统接口就能读取。length就是这个字节流的长度,单位字节。image参数是一个SkBitmap对象,用于存储解码后的位图结果。在外部实例化一个空的SkBitmap,并把指针传进来就行。像这样: SkBitmap image;

或 new 一个: SkBitmap* image = new SkBitmap();

这里主要的坑点是对包含透明度的图片,Skia只支持预乘透明度格式(Premultiplied Alpha)的图片渲染。具体什么是 Premultiplied Alpha,大家可以自行谷歌一下,是一种常见的渲染上的性能优化方式。我们的位图文件通常都不是按 Premultiplied Alpha 方式存储的,所以这里 codec

直接分析出来的 AlphaType 一般也是不能用的。所以要判断一下,图片是否含有透明通道,如果有的话,强制把 AlphaType 改为 Premultiplied Alpha 的,也就是这句: info = info.makeAlphaType(kPremul_SkAlphaType);

这样解码出来的图片就能正确渲染了。

再来看一下位图编码:

void encode(const SkBitmap& image) {

auto encodeType = SkImageEncoder::kJPEG_Type;

auto compressionQuality = 92;

SkDynamicMemoryWStream stream;

auto result = SkImageEncoder::EncodeStream(&stream, image, encodeType, compressionQuality);

if (result) {

auto length = stream.bytesWritten();

auto bytes = new uint8_t[length];

stream.copyTo(bytes);

// do something with the bytes ...

}

}

上面这个简单的示例展示了如何把一个 SkBitmap 对象编码为 JPEG 格式的图片文件字节流。当然你也可以把 encodeType

改为其他的格式,比如: SkImageEncoder::kPNG_Type

SkImageEncoder::kWEBP_Type

compressionQuality

表示图片的压缩质量,这个参数对PNG格式无效,因为PNG是无损的。最后我们会得到一个 bytes

字节流首地址,以及一个 length

字节流长度。需要存储文件就直接保存这个字节流即可。

这里的坑点是你直接这么调用的话,结果会总是编码失败。断点进去看运行过程就会发现因为缺少对应类型的编码器,实际上一个编码器都不存在。这是因为 SkImageEncoder

采用的是注册机制,这是被动调用的(你也可以注册自己的编码器进去)。虽然Skia默认提供了各种格式的编码器,也写好了注册代码,但是你自己的最终项目里因为没直接调用过那些编码器,只调用了 SkImageEncoder

, C++在链接你的程序的时候就会抛弃库里的实际编码器,因为你没调用过。所以解决方案是显式调用一下。好在Skia给我们写了一个专门用来欺骗编译器的宏。

#include <SkForceLinking.h>

__SK_FORCE_IMAGE_DECODER_LINKING;

你只需要像上面那样,包含 SkForceLinking.h

的头文件,然后在工程里任何一个地方加上这句宏的调用即可: __SK_FORCE_IMAGE_DECODER_LINKING;

。这样之前的代码就可以正常编码位图了。

未登录用户
全部评论0
到底啦