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;}}
#endiffChain = 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
解码模式,按函数参数
传入.