美文网首页
Bitmap解析流程

Bitmap解析流程

作者: gczxbb | 来源:发表于2018-05-16 23:23 被阅读5次

Bitmap是位图,每个像素点根据位的深度表示不同种类的颜色,深度越大,代表的颜色值越多,存储空间越大。


BitmapFactory工厂

从文件读取。

public static Bitmap decodeFile(String pathName)
public static Bitmap decodeFile(String pathName, Options opts)

根据路径,创建一个FileInputStream,然后,调用decodeStream方法。
从资源读取。

public static Bitmap decodeResource(Resources res, int id, Options opts) 
public static Bitmap decodeResource(Resources res, int id)
public static Bitmap decodeResourceStream(Resources res, TypedValue value,InputStream is, Rect pad, Options opts)

前两个方法,通过Resources的openRawResource(id, value)方法,根据资源id,调用AssetManager的openNonAsset方法,创建AssetInputStream。然后,他们再去调用decodeResourceStream方法。最终,这三个方法全部触发decodeStream。
从字节数组读取。

public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts)
public static Bitmap decodeByteArray(byte[] data, int offset, int length)

调用本地nativeDecodeByteArray方法。
从流读取。

public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts)
public static Bitmap decodeStream(InputStream is)

资源解析id成AssetInputStream流,调用本地nativeDecodeAsset方法。其他流调用decodeStreamInternal方法,调用本地nativeDecodeStream方法。
从文件描述符读取。

public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts)
public static Bitmap decodeFileDescriptor(FileDescriptor fd)

调本地nativeDecodeFileDescriptor方法,或创建FileInputStream流解析,与文件读取类似,调用decodeStreamInternal方法。

综上所述

BitmapFactory提供了解析文件、资源、字节数组、文件描述符、或流的方式创建Bitmap,在创建时,Option配置将影响它占用的内存。以上就是几种主要的解析方式,下面我们根据源码看一下InputStream是如何解析。


InputStream流解析

看一下BitmapFactory的decodeStream方法。

public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
    Bitmap bm = null;
    try {
        if (is instanceof AssetManager.AssetInputStream) {
            final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
            bm = nativeDecodeAsset(asset, outPadding, opts);//根据InputStream不同。
        } else {
            bm = decodeStreamInternal(is, outPadding, opts);
        }
        ...
        setDensityFromOptions(bm, opts);
    } finally {
    }
    return bm;
}

它的三个参数,InputStream、Rect和Option,Options是工厂的静态内部类,包含一些解析的配置信息,将这些信息传到底层。AssetInputStream流调用nativeDecodeAsset方法,其他的调用nativeDecodeStream方法。

private static Bitmap decodeStreamInternal(InputStream is, Rect outPadding, Options opts) {
    byte [] tempStorage = null;
    if (opts != null) tempStorage = opts.inTempStorage;
    if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE];
    return nativeDecodeStream(is, tempStorage, outPadding, opts);
}

字节数组大小16 * 1024。

static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
        jobject padding, jobject options) {
    jobject bitmap = NULL;
    SkAutoTDelete<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage))
    if (stream.get()) {
        SkAutoTDelete<SkStreamRewindable> bufferedStream(
                SkFrontBufferedStream::Create(stream.detach(), BYTES_TO_BUFFER));
        bitmap = doDecode(env, bufferedStream, padding, options);
    }
    return bitmap;
}

该JNI#方法的第一个参数是InputStream数据输入流,第二个参数是storage字节数组,padding是null,第四个参数是Bitmap配置Options。然后,将上层传入的InputStream转换底层SkStreamRewindable,JNI#方法doDecode解析bufferedStream,生成一个jobject对象,它就是Java层的Bitmap。看一下JNI#方法doDecode。该方法比较长,分成四个部分。

第一部分代码
static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
    int sampleSize = 1;
    SkImageDecoder::Mode decodeMode = SkImageDecoder::kDecodePixels_Mode;
    SkColorType prefColorType = kN32_SkColorType;

    bool doDither = true;
    bool isMutable = false;
    float scale = 1.0f;
    bool preferQualityOverSpeed = false;
    bool requireUnpremultiplied = false;

    if (options != NULL) {
        //解析Option的inSampleSize,图片读取到内存中的像素压缩的比例。
        sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
        //解析Option的inJustDecodeBounds,只获取图片信息,不读像素模式。
        if (optionsJustBounds(env, options)) {
            decodeMode = SkImageDecoder::kDecodeBounds_Mode;
        }
        //初始化Option的outWidth与outHeight为-1。outMimeType为0。
        env->SetIntField(options, gOptions_widthFieldID, -1);
        env->SetIntField(options, gOptions_heightFieldID, -1);
        env->SetObjectField(options, gOptions_mimeFieldID, 0);
        //Bitmap.Config配置inPreferredConfig。
        jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
        //解析出SkColorType。
        prefColorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig);
        //Java层值读取
        isMutable = env->GetBooleanField(options, gOptions_mutableFieldID);
        doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
        preferQualityOverSpeed = env->GetBooleanField(options,gOptions_preferQualityOverSpeedFieldID);
        requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID);
        //Java层inBitmap对象
        javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
        //Option的inScaled,默认true
        if (env->GetBooleanField(options, gOptions_scaledFieldID)) { 
            //Option的inDensity
            const int density = env->GetIntField(options, gOptions_densityFieldID);
            //Option的inTargetDensity
            const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
            //Option的inScreenDensity
            const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
            if (density != 0 && targetDensity != 0 && density != screenDensity) {
                scale = (float) targetDensity / density;
            }
        }
    }
    const bool willScale = scale != 1.0f;
    ...
    //后续代码
}

当Java层Option参数不空时候,读取参数信息,主要Option信息包括。
inSampleSize,图片读取到内存中的像素采样率,即图片的压缩比,inSampleSize是2代表图像的长宽都变为原始的1/2,该值根据图片的真实像素宽高和ImageView的宽高共同计算得出。
inJustDecodeBounds,该参数表示解析时只读取图片信息,如图片尺寸,不分配像素内存空间,SkImageDecoder模式是kDecodeBounds_Mode。
isMutable,可变的,生成Bitmap可变位图,可修改像素,默认是false。
inPreferredConfig:Bitmap.Config图片像素类型,基本类型有ALPHA_8、RGB_565、ARGB_4444、ARGB_8888。默认是ARGB_8888,它的图片质量较高,每像素4个字节,内存占用最大。可配置成RGB_565类型,2个字节。
outHeight/outWidth,图像宽高,初始值设置-1。
inBitmap,重用bitmap,如果在Java层设置了这个参数,新Bitmap会使用该参数的内存空间,Option#isMutable必须是可变的。
prefColorType,根据上层Config的nativeInt变量,解析SkColorType类型,与Config像素类型对应,kRGB_565_SkColorType、kIndex_8_SkColorType、kN32_SkColorType等。
inScaled,inDensity,inTargetDensity,inScreenDensity,由它们决定图片是否缩放,首先是inScaled决定是否缩放,默认缩放,然后,如果配置了inTargetDensity和inDensity,并且inScreenDensity和inDensity不相等,按照比例inTargetDensity/inDensity缩放,图片去适应屏幕。最后的结果scale不是1,将设置willScale参数。
inDensity,图片像素密度。
inTargetDensity,目标像素密度。根据该值与Bitmap的像素密度的比值,确定缩放比例。
inScreenDensity,屏幕像素密度。
当我们从资源加载图片时,设置inTargetDensity值为densityDpi,因此,不同屏幕像素密度手机从资源文件加载图片时缩放比不同,载入内存也是不同的。densityDpi值以160像素为基准,240,320,480,640。

第二部分代码
SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
//decoder解析器是空返回null
//SkImageDecoder设置sampleSize、doDither、
//preferQualityOverSpeed、requireUnpremultiplied等参数。
decoder->setSampleSize(sampleSize);
...
android::Bitmap* reuseBitmap = nullptr;
unsigned int existingBufferSize = 0;
if (javaBitmap != NULL) {
    reuseBitmap = GraphicsJNI::getBitmap(env, javaBitmap);
    //这个bitmap不可变
    if (reuseBitmap->peekAtPixelRef()->isImmutable()) {//不可变的bitmap,无法重用
        javaBitmap = NULL;
        reuseBitmap = nullptr;
    } else {
        existingBufferSize = GraphicsJNI::getBitmapAllocationByteCount(env, javaBitmap);
    }
}
...
JavaPixelAllocator javaAllocator(env);
RecyclingPixelAllocator recyclingAllocator(reuseBitmap, existingBufferSize);
ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize);
SkBitmap::Allocator* outputAllocator = (javaBitmap != NULL) ?
            (SkBitmap::Allocator*)&recyclingAllocator : (SkBitmap::Allocator*)&javaAllocator;
if (decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
    if (!willScale) {
        decoder->setSkipWritingZeroes(outputAllocator == &javaAllocator);
        decoder->setAllocator(outputAllocator);
    } else if (javaBitmap != NULL) {
        decoder->setAllocator(&scaleCheckingAllocator);
    }
}

创建解析器SkImageDecoder,设置从Option读取到部分配置。获取上层重用javaBitmap的底层Bitmap对象,如果不可变,不能重用,置空。如果可重用,解析javaBitmap内存字节大小existingBufferSize。
在非kDecodeBounds_Mode时,读取像素到内存,SKImageDecoder的setAllocator方法,设置Allocator内存分配器。包括三种,JavaPixelAllocator,RecyclingPixelAllocator和ScaleCheckingAllocator。
当不需要缩放时,重用Bitmap,使用RecyclingPixelAllocator,不重用Bitmap,使用JavaPixelAllocator。
当需要缩放,重用Bitmap,使用ScaleCheckingAllocator。
只有RecyclingPixelAllocator和ScaleCheckingAllocator传入existingBufferSize大小,与重用Bitmap相关。

第三部分代码
SkAutoTDelete<SkImageDecoder> add(decoder);
AutoDecoderCancel adc(options, decoder);

SkBitmap decodingBitmap;
//stream解析到skbitmap
if (decoder->decode(stream, &decodingBitmap, prefColorType, decodeMode)
            != SkImageDecoder::kSuccess) {
    return nullObjectReturn("decoder->decode returned false");
}
//拿到宽高
int scaledWidth = decodingBitmap.width();
int scaledHeight = decodingBitmap.height();
//Scale处理后的宽高
if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
    scaledWidth = int(scaledWidth * scale + 0.5f);
    scaledHeight = int(scaledHeight * scale + 0.5f);
}
//得到了宽和高和mimeType,设置option
if (options != NULL) {
    jstring mimeType = getMimeTypeString(env, decoder->getFormat());
    if (env->ExceptionCheck()) {
        return nullObjectReturn("OOM in getMimeTypeString()");
    }
    env->SetIntField(options, gOptions_widthFieldID, scaledWidth);
    env->SetIntField(options, gOptions_heightFieldID, scaledHeight);
    env->SetObjectField(options, gOptions_mimeFieldID, mimeType);
}
//inJustDecodeBounds为true,直接返回NULL
if (decodeMode == SkImageDecoder::kDecodeBounds_Mode) {
    return NULL;
}
//NinePatchPeeker相关处理
...

SkImageDecode的decode方法,解码stream后,得到SkBitmap类型的对象。
获取SkBitmap的宽高,如果需要缩放,并且非kDecodeBounds_Mode模式,将重新计算的scaledWidth、scaledHeight和mimeType信息设置到Java层Option对象。
如果是kDecodeBounds模式,表示Java层Option设置inJustDecodeBounds参数,不需要分配内存,返回空,图片信息已保存在Option对象。

第四部分代码
SkBitmap outputBitmap;
if (willScale) {
    //计算Scale的比例。
    const float sx = scaledWidth / float(decodingBitmap.width());
    const float sy = scaledHeight / float(decodingBitmap.height());

    SkColorType colorType = colorTypeForScaledOutput(decodingBitmap.colorType());  
    outputBitmap.setInfo(SkImageInfo::Make(scaledWidth, scaledHeight,
            colorType, decodingBitmap.alphaType()));
    if (!outputBitmap.tryAllocPixels(outputAllocator, NULL)) {
        return nullObjectReturn("allocation failed for scaled bitmap");
    }
   
    if (outputAllocator != &javaAllocator) {
        outputBitmap.eraseColor(0);
    }

    SkPaint paint;
    paint.setFilterQuality(kLow_SkFilterQuality);

    SkCanvas canvas(outputBitmap);
    canvas.scale(sx, sy);
    canvas.drawARGB(0x00, 0x00, 0x00, 0x00);
    canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
} else {
    outputBitmap.swap(decodingBitmap);
}

if (outputBitmap.pixelRef() == NULL) {
    return nullObjectReturn("Got null SkPixelRef");
}

if (!isMutable && javaBitmap == NULL) {
    outputBitmap.setImmutable();
}

if (javaBitmap != NULL) {
    bool isPremultiplied = !requireUnpremultiplied;
    GraphicsJNI::reinitBitmap(env, javaBitmap, outputBitmap.info(), isPremultiplied);
    outputBitmap.notifyPixelsChanged();
    return javaBitmap;
}

int bitmapCreateFlags = 0x0;
if (isMutable) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Mutable;
if (!requireUnpremultiplied) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Premultiplied;

// now create the java bitmap
return GraphicsJNI::createBitmap(env, javaAllocator.getStorageObjAndReset(),
                bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);

SkImageInfo是一个结构体,包含宽高、颜色类型、透明类型等信息,Make方法,创建该结构体,SKBitmap的setInfo方法设置内部SkImageInfo对象。
如果不缩放,将前面解析的decodingBitmap交给输出outputBitmap。
如果重用javaBitmap不空,利用输出的outputBitmap重新初始化该对象,通知像素改变,将该Java层Bitmap对象返回。
如果不重用,GraphicsJNI的createBitmap方法,创建Java层的Bitmap实例。

jobject GraphicsJNI::createBitmap(JNIEnv* env, android::Bitmap* bitmap,
        int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets,
        int density) {
    ...
    jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
            reinterpret_cast<jlong>(bitmap), bitmap->javaByteArray(),
            bitmap->width(), bitmap->height(), density, isMutable, isPremultiplied,
            ninePatchChunk, ninePatchInsets);
    return obj;
}

env的NewObject方法,创建Bitmap实例,入参是native层bitmap指针,Java层字节数组的强引用,bitmap宽/高,density,isMutable,isPremultiplied,ninePatchChunk,ninePatchInsets。
Java层字节数组强引用是mPixelStorage中结构体java的jbyteArray变量。在使用JavaPixelAllocator分配器分配内存时,会介绍如何对结构体赋值。

// called from JNI
Bitmap(long nativeBitmap, byte[] buffer, int width, int height, int density, boolean isMutable, 
                boolean requestPremultiplied, byte[] ninePatchChunk, 
                NinePatch.InsetStruct ninePatchInsets)

最终,Java层Bitmap实例创建成功,构造方法是由底层JNI方法调用。


了解SKImageDecode解码

我们前面说过,通过解码器SkImageDecoder的decode方法,解析stream,得到SkBitmap对象。

SkImageDecoder::Result SkImageDecoder::decode(SkStream* stream, SkBitmap* bm,
                    SkColorType pref,Mode mode) {
    fShouldCancelDecode = false;
    fDefaultPref = pref;
    SkBitmap tmp;
    const Result result = this->onDecode(stream, &tmp, mode);
    if (kFailure != result) {
        bm->swap(tmp);
    }
    return result;
}

调用的onDecode方法在它的子类里实现,子类包括各种图片格式png,jpeg及gif的解码类。比如,SkPNGImageDecoder继承SkImageDecoder,看一下它的onDecode方法。

SkImageDecoder::Result SkPNGImageDecoder::onDecode(SkStream* sk_stream, 
                    SkBitmap* decodedBitmap,Mode mode) {
    //初始化
    if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
        return kFailure;
    }
    ....
    //该模式直接返回成功
    if (SkImageDecoder::kDecodeBounds_Mode == mode) {
        return kSuccess;
    }
    ....
    //内存分配
    //最终触发Allocator的allocPixelRef方法,入参decodedBitmap
    if (!this->allocPixelRef(decodedBitmap,
                kIndex_8_SkColorType == colorType ? colorTable : NULL)) {
        return kFailure;
    }
    png_read_update_info(png_ptr, info_ptr);
    ....
    //像素读取
    ....
    png_read_end(png_ptr, info_ptr);
    ....
    return kSuccess;
}

调用它自己的allocPixelRef方法。

bool SkImageDecoder::allocPixelRef(SkBitmap* bitmap,
                    SkColorTable* ctable) const {
    return bitmap->tryAllocPixels(fAllocator, ctable);
}

该方法调用入参SkBitmap的tryAllocPixels方法,将SkImageDecoder内部的分配器传入。在SkImageDecoder创建后会setAllocator初始化Allocator,前面代码都有写。

bool SkBitmap::tryAllocPixels(Allocator* allocator, SkColorTable* ctable) {
    HeapAllocator stdalloc;
    if (NULL == allocator) {
        allocator = &stdalloc;
    }
    return allocator->allocPixelRef(this, ctable);
}

最终,在SkBitmap类中,调用Allocator的allocPixelRef方法,将SKBitmap作为参数传入。以JavaPixelAllocator分配器为例,在Java堆上分配内存。

class JavaPixelAllocator : public SkBitmap::Allocator {
public:
    JavaPixelAllocator(JNIEnv* env);
    ~JavaPixelAllocator();

    virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) override;

    android::Bitmap* getStorageObjAndReset() {
        android::Bitmap* result = mStorage;
        mStorage = NULL;
        return result;
    };

private:
    JavaVM* mJavaVM;
    android::Bitmap* mStorage = nullptr;
};

JavaPixelAllocator的allocPixelRef方法

bool JavaPixelAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
    JNIEnv* env = vm2env(mJavaVM);
    mStorage = GraphicsJNI::allocateJavaPixelRef(env, bitmap, ctable);
    return mStorage != nullptr;
}

GraphicsJNI的allocateJavaPixelRef方法。创建底层Bitmap对象,指针赋值mStorage,在JavaPixelAllocator中保存。

android::Bitmap* GraphicsJNI::allocateJavaPixelRef(JNIEnv* env, 
                    SkBitmap* bitmap,SkColorTable* ctable) {
    const SkImageInfo& info = bitmap->info();

    size_t size;
    if (!computeAllocationSize(*bitmap, &size)) {
        return NULL;
    }
    const size_t rowBytes = bitmap->rowBytes();
    jbyteArray arrayObj = (jbyteArray) env->CallObjectMethod(
                gVMRuntime, gVMRuntime_newNonMovableArray,
                gByte_class, size);//Java堆分配内存
    jbyte* addr = (jbyte*) env->CallLongMethod(gVMRuntime, 
                    gVMRuntime_addressOf, arrayObj);//地址
    android::Bitmap* wrapper = new android::Bitmap(env, arrayObj, (void*) addr,
            info, rowBytes, ctable);//创建native层Bitmap
    wrapper->getSkBitmap(bitmap);//用native层Bitmap初始化SkBitmap
    bitmap->lockPixels();

    return wrapper;
}

GraphicsJNI的computeAllocationSize方法,根据SkBitmap的height和width计算分配空间大小,存储在size。
VMRuntime的newNonMovableArray方法,在Java堆分配内存,返回Java层字节数组arrayObj。
VMRuntime的addressOf方法,返回jbyteArray字节数组arrayObj的地址。
VMRuntime源码/libcore/libart/src/main/java/dalvik/system/VMRuntime.java目录。
创建底层的Bitmap。

Bitmap::Bitmap(JNIEnv* env, jbyteArray storageObj, void* address,
        const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable)
        : mPixelStorageType(PixelStorageType::Java) {
    env->GetJavaVM(&mPixelStorage.java.jvm);
    mPixelStorage.java.jweakRef = env->NewWeakGlobalRef(storageObj);
    mPixelStorage.java.jstrongRef = nullptr;//数组强引用暂时置空。
    mPixelRef.reset(new WrappedPixelRef(this, address, info, rowBytes, ctable));
 
    mPixelRef->unref();
}

底层Bitmap的两个变量mPixelStorage和mPixelRef。
mPixelStorage,联合体,内部的java结构体内容。jweakRef指向NewWeakGlobalRef方法创建的Java层字节数组(Java堆对象)的弱全局引用。mPixelRef,WrappedPixelRef,继承SkPixelRef,封装Bitmap本身,地址addr,像素大小rowBytes,SkImageInfo等。

struct {
    JavaVM* jvm;
    jweak jweakRef;
    jbyteArray jstrongRef;
} java;

底层Bitmap创建后,调用getSkBitmap方法,初始化SkBitmap输出。

void Bitmap::getSkBitmap(SkBitmap* outBitmap) {
    outBitmap->setInfo(mPixelRef->info(), mPixelRef->rowBytes());
    outBitmap->setPixelRef(refPixelRefLocked())->unref();//获取mPixelRef引用
    outBitmap->setHasHardwareMipMap(hasHardwareMipMap());
}

底层Bitmap的refPixelRefLocked方法,获取mPixelRef引用。SkBitmap内部包含mPixelRef引用。

JavaPixelAllocator分配内存.jpg
综上所述

Android6.0的Bitmap源码,位图像素存储在Java堆字节数组中,Java层的Bitmap对象引用底层Bitmap,字节数组同时被Java层Bitmap和底层Bitmap#mPixelStorage引用


任重而道远

相关文章

网友评论

      本文标题:Bitmap解析流程

      本文链接:https://www.haomeiwen.com/subject/nhkudftx.html