2311skia,06编解码图像上
1
,API
和自注册机制
Skia
中只需要一行代码就可编解码
图片:
SkBitmap bitmap;
SkImageDecoder::DecodeFile("test.xxx", &bitmap);//按文件名解码,自动推导`图片类型`
//或按流解码
SkFILEStream stream("test.xxx");
SkImageDecoder::DecodeStream(stream, &bitmap);//由`输入流`解码,`自动推导`图片类型编码
//
SkImageEncoder::EncodeFile("test.jpg", bitmap, SkImageEncoder::kJPEG_Type, 90/*编码图片质量,对`jpeg`格式和`webp`格式有用*/);
设计是用抽象工厂模式
产生编码器,解码器
实例,用自注册
方式注入
产生函数,使之在加载库
时即初化
完成.
template <typename T> class SkTRegistry : SkNoncopyable {
public:
typedef T Factory;
explicit SkTRegistry(T fact) : fFact(fact) {
#ifdef SK_BUILD_FOR_ANDROID
//两次初化错误的变通
{
SkTRegistry* reg = gHead;
while (reg) {
if (reg == this) {
return;
}
reg = reg->fChain;
}
}
#endif
fChain = gHead;
gHead = this;
}
static const SkTRegistry* Head() { return gHead; }
const SkTRegistry* next() const { return fChain; }
const Factory& factory() const { return fFact; }
private:
Factory fFact;
SkTRegistry* fChain;
static SkTRegistry* gHead;
};
//调用者仍需要在某处声明此实例
template <typename T> SkTRegistry<T>* SkTRegistry<T>::gHead;
SkImageDecoder
中规定的解码器工厂注册函数:
typedef SkTRegistry<SkImageDecoder*(*)(SkStreamRewindable*)> SkImageDecoder_DecodeReg;
按如下方法注册gif
的解码器工厂
:
static bool is_gif(SkStreamRewindable* stream) {
char buf[GIF_STAMP_LEN];
if (stream->read(buf, GIF_STAMP_LEN) == GIF_STAMP_LEN) {
if (memcmp(GIF_STAMP, buf, GIF_STAMP_LEN) == 0 ||
memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 ||
memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0) {
return true;
}
}
return false;
}
static SkImageDecoder* sk_libgif_dfactory(SkStreamRewindable* stream) {
if (is_gif(stream)) {
return SkNEW(SkGIFImageDecoder);
}
return NULL;
}
static SkImageDecoder_DecodeReg gReg(sk_libgif_dfactory);
2
,解码流程:
bool SkImageDecoder::DecodeStream(SkStreamRewindable* stream, SkBitmap* bm, SkColorType pref, Mode mode, Format* format) {
SkASSERT(stream);
SkASSERT(bm);
bool success = false;
SkImageDecoder* codec = SkImageDecoder::Factory(stream);
if (NULL != codec) {
success = codec->decode(stream, bm, pref, mode);
if (success && format) {
*format = codec->getFormat();
if (kUnknown_Format == *format) {
if (stream->rewind()) {
*format = GetStreamFormat(stream);
}
}
}
delete codec;
}
return success;
}
bool SkImageDecoder::decode(SkStream* stream, SkBitmap* bm, SkColorType pref, Mode mode) {
//调用`onDecode`前,重置为`false`
fShouldCancelDecode = false;
//赋值此值,供`getPrefColorType()`使用,以防`fUsePrefTable`为`false`
fDefaultPref = pref;
//传递一个临时位图,这样,如果返回`false`,就可确保`调用者`的位图不变.
SkBitmap tmp;
if (!this->onDecode(stream, &tmp, mode)) {
return false;
}
bm->swap(tmp);
return true;
}
(1)
遍历所有解码器
,找到支持该文件
的解码器
:
遍历
代码见:
external/skia/src/images/SkImageDecoder_FactoryRegistrar.cpp
SkImageDecoder* SkImageDecoder::Factory(SkStreamRewindable* stream) {
return image_decoder_from_stream(stream);
}
SkImageDecoder* image_decoder_from_stream(SkStreamRewindable* stream) {
SkImageDecoder* codec = NULL;
const SkImageDecoder_DecodeReg* curr = SkImageDecoder_DecodeReg::Head();
while (curr) {
codec = curr->factory()(stream);
//在此倒带,因为承诺稍后当调用"解码"时,流在开始位置.
bool rewindSuceeded = stream->rewind();
//`图像解码器`要求支持倒带,否则如果得到不支持`倒带`的流,会提前失败.
if (!rewindSuceeded) {
SkDEBUGF(("Unable to rewind the image stream."));
SkDELETE(codec);
return NULL;
}
if (codec) {
return codec;
}
curr = curr->next();
}
return NULL;
}
(2)
解码器调用相应的解码库函数
(简单的图片如bmp
自行处理),解出原始图片,见各个解码器的onDecode
方法
(3)
在onDecode
方法中,一般需要用SkScaledBitmapSampler
类,用来处理后续缩放图片
及预乘透明度
.
编码流程
相对简单,就是根据类型
取相应编码器并编码
文件,不详述.
3
,解码中流的设置:
从框架
层调Skia
的解码,主要是以下几个函数:
nativeDecodeStream
:以Java
的InputStream
类作为输入解码,创建JavaInputStreamAdaptor
流(其实现是先回调Java
中InputStream
的读取方法,读其到缓存区
,再从缓存区
中读到目标内存
.)
nativeDecodeFileDescriptor
:以文件描述符
为输入解码
,创建SkFILEStream
,直接读取文件
内容
nativeDecodeAsset
:以Asset
作为输入解码,创建AssetStreamAdaptor(frameworks/base/core/jni/android/graphics/Utils.cpp)
.
nativeDecodeByteArray
:以ByteArray
作为输入解码,创建SkMemoryStream
,来直接从内存
中读.
#define BYTES_TO_BUFFER 64
static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
jobject padding, jobject options) {
jobject bitmap = NULL;
SkAutoTUnref<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage));
if (stream.get()) {
SkAutoTUnref<SkStreamRewindable> bufferedStream( SkFrontBufferedStream::Create(stream, BYTES_TO_BUFFER));
SkASSERT(bufferedStream.get() != NULL);
bitmap = doDecode(env, bufferedStream, padding, options);
}
return bitmap;
}
static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fileDescriptor,
jobject padding, jobject bitmapFactoryOptions) {
NPE_CHECK_RETURN_ZERO(env, fileDescriptor);
int descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor);
struct stat fdStat;
if (fstat(descriptor, &fdStat) == -1) {
doThrowIOE(env, "broken file descriptor");
return nullObjectReturn("fstat return -1");
}
//在退出此函数时,恢复`描述符`的偏移.即使重复了描述符,`原始和重复`描述符都引用了相同的`打开文件描述`,且更改一个文件的`偏移`会影响另一个.
AutoFDSeek autoRestore(descriptor);
//在此`复制`描述符以`避免内存泄漏`.如果只关闭`文件描述符`而不关闭`创建`它的`文件对象`,就会泄漏.如果不`显式`清理文件(反之,又关闭了描述符),就会泄露`fseek`内部分配的缓冲.
int dupDescriptor = dup(descriptor);
FILE* file = fdopen(dupDescriptor, "r");
if (file == NULL) {
//清理重复描述符,因为`(fclose)`在`清理文件`时,不会关闭它.
close(dupDescriptor);
return nullObjectReturn("Could not open file");
}
SkAutoTUnref<SkFILEStream> fileStream(new SkFILEStream(file, SkFILEStream::kCallerPasses_Ownership));
//使用缓冲流.尽管可倒带`SkFILEStream`,但这可确保`SkImageDecoder::Factory`在超出文件描述符的当前位置时,永远不倒带.
SkAutoTUnref<SkStreamRewindable> stream(SkFrontBufferedStream::Create(fileStream, BYTES_TO_BUFFER));
return doDecode(env, stream, padding, bitmapFactoryOptions);
}
因为以文件描述符
和InputStream
为输入时,不能保证可倒带
该流,因此加了一层SkFrontBufferedStream
的包装,来缓存
输入流中最前面
的一段区域(#define BYTES_TO_BUFFER 64)
,以便在读取这段区域
时可倒带(rewind)
,来让解码器
读完文件头
判断类型
后可倒带.
详细见:
external/skia/src/utils/SkFrontBufferedStream.cpp
4
,解码选项
external/skia/include/core/SkImageDecoder.h
class SkImageDecoder : SkNoncopyable {
private:
Peeker* fPeeker;//仅在解`ninepatch`图片中使用,来提取子图片断,仅对`png`格式有效
SkBitmap::Allocator* fAllocator;//解码需要产生`SkBitmap`,这里是其像素的内存分配器
int fSampleSize;//图片下采样率,为2的幂次,该设计主要针对的是`jpeg`格式的图像.`Jpeg`格式编码时以`8*8`像素单位为一个`块`作傅立叶变换,设成`2,4,8`时可针对性简化反傅立叶变换`(idct)`.
SkColorType fDefaultPref;//优先选取的解码出来的图片格式
bool fDitherImage;//是否做抖动处理,主要针对`16`位色,在解码`png`和`jpeg`时生效
bool fSkipWritingZeroes;//是否默认,结果图已清零并不写入像素值零的点.该选项仅在`SkScaledBitmapSampler`中有判断,如`(get_RGBA_to_8888_proc`函数),来节省计算`后续缩放`,意义不大.(真要优化不如用`simd`覆盖,如`neon`)
mutable bool fShouldCancelDecode;//比如用户在未解析完`A图片`时,已划过A图片所在区域,此时可设置`fShouldCancelDecode`,来取消解码`A图片`.
bool fPreferQualityOverSpeed;//只对`jpeg`格式有用,决定反傅立叶变换`(idct)`的运算精度,速度优先时,以8位精度整数替代浮点运算,质量优先时,以`16`位精度整数替代浮点运算
bool fRequireUnpremultipliedColors;//解带透明度的图片,如`png`时有用,表示是否用透明度预乘`图片像素`,默认是`预乘`,以便`渲染`时不用再乘.
};
这些私有变量
对应BitmapFactory.Options
中的相应的配置项
,见
frameworks/base/graphics/java/android/graphics/BitmapFactory.java
设置的代码见doDecode
方法:
frameworks/base/core/jni/android/graphics/BitmapFactory.cpp
static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
/*`......`*/
SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
if (decoder == NULL) {
return nullObjectReturn("SkImageDecoder::Factory returned null");
}
decoder->setSampleSize(sampleSize);
decoder->setDitherImage(doDither);
decoder->setPreferQualityOverSpeed(preferQualityOverSpeed);
decoder->setRequireUnpremultipliedColors(requireUnpremultiplied);
/*`......`*/
}
在SkImageDecoder
中并不反映全部Java
层的配置项,而是如Mode
解码模式,按函数参数
传入.