剑客
关注科技互联网

调用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;
。这样之前的代码就可以正常编码位图了。

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址