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 = imageInfo.isOpaque() ? kOpaque_SkAlphaType : kPremul_SkAlphaType;
auto info = SkImageInfo::Make(imageInfo.width(), imageInfo.height(), kN32_SkColorType, alphaType);
auto result = codec->getPixels(info, image->getPixels(), image->rowBytes());
return result == SkCodec::kSuccess;
}
上面这个函数就能实现调用Skia对字节流数据进行位图解码。解释一下几个参数: bytes是位图文件的字节流首地址,这个很简单,通过各种标准文件系统接口就能读取。length就是这个字节流的长度,单位字节。image参数是一个SkBitmap对象,用于存储解码后的位图结果。在外部实例化一个空的SkBitmap,并把指针传进来就行。
这里主要的坑点是对包含透明度的图片,Skia只支持预乘透明度格式(Premultiplied Alpha)的图片渲染。具体什么是 Premultiplied Alpha,大家可以自行谷歌一下,是一种常见的渲染上的性能优化方式。我们的位图文件通常都不是按 Premultiplied Alpha 方式存储的,所以这里 codec
直接分析出来的 SkImageInfo 一般也是不能用的。所以要判断一下,图片是否含有透明通道,如果有的话,强制把 alphaType 改为 kPremul_SkAlphaType 的,也就是这句:auto alphaType = imageInfo.isOpaque() ? kOpaque_SkAlphaType : 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;
。这样之前的代码就可以正常编码位图了。