当前位置: 首页 > article >正文

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:以JavaInputStream类作为输入解码,创建JavaInputStreamAdaptor流(其实现是先回调JavaInputStream的读取方法,读其到缓存区,再从缓存区中读到目标内存.)

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


http://www.kler.cn/a/145655.html

相关文章:

  • PHP echo和print 语句
  • 可以在Playgrounds或Xcode Command Line Tool开始学习Swift
  • Screen操作
  • vscode在运行c语言时,无法scanf输入
  • 2018年11月8日 Go生态洞察:参与2018年Go用户调查
  • SpringBoot——定制错误页面及原理
  • leetcode刷题详解五
  • 乐观锁解决库存超卖问题
  • 【超强笔记软件】Obsidian如何实现免费无限流量无套路云同步?
  • mybatis的使用,mybatis的实现原理,mybatis的优缺点,MyBatis缓存,MyBatis运行的原理,MyBatis的编写方式
  • ESP32网络开发实例-远程Web串口监视器
  • 声音响度、声压级计权(A B C)实现
  • 高品质MP3音频解码语音芯片WT2003Hx的特征优势与应用场景
  • WebSocket了解
  • 论文公式和代码对应
  • C语言数据类型和变量
  • 机器学习/sklearn笔记:MeanShift
  • SkyWalking全景解析:从原理到实现的分布式追踪之旅
  • DY点赞、搜索功能测试用例设计
  • 【刷题笔记】接雨水||暴力通过||符合思维方式