深入理解Android Bitmap
Bitmap (android.graphics.Bitmap)
Bitmap是Android系統(tǒng)中的圖像處理的最重要類(lèi)之一。用它可以獲取圖像文件信息,進(jìn)行圖像剪切、旋轉(zhuǎn)、縮放等操作,并可以指定格式保存圖像文件。
基于android-6.0.1_r80源代碼分析
通過(guò)下面三個(gè)章節(jié)基本可以掃清 Bitmap 盲區(qū)。文章沒(méi)有覆蓋到的一方面是Bitmap用法,這部分建議閱讀 Glide 庫(kù)源代碼。一些 Color 的概念,例如 premultiplied / Dither ,需要具備一定CG物理基礎(chǔ),不管怎樣先讀下去。
Bitmap對(duì)象創(chuàng)建
Bitmap java 層構(gòu)造函數(shù)是通過(guò) native 層 jni call 過(guò)來(lái)的,邏輯在 Bitmap_creator 方法中。
// /home/yuxiang/repo_aosp/android-6.0.1_r79/frameworks/base/core/jni/android/graphics/Bitmap.cpp static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors, jint offset, jint stride, jint width, jint height, jint configHandle, jboolean isMutable) { SkColorType colorType = GraphicsJNI::legacyBitmapConfigToColorType(configHandle); if (NULL != jColors) { size_t n = env->GetArrayLength(jColors); if (n < SkAbs32(stride) * (size_t)height) { doThrowAIOOBE(env); return NULL; } } // ARGB_4444 is a deprecated format, convert automatically to 8888 if (colorType == kARGB_4444_SkColorType) { colorType = kN32_SkColorType; } SkBitmap bitmap; bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType)); Bitmap* nativeBitmap = GraphicsJNI::allocateJavaPixelRef(env, &bitmap, NULL); if (!nativeBitmap) { return NULL; } if (jColors != NULL) { GraphicsJNI::SetPixels(env, jColors, offset, stride, 0, 0, width, height, bitmap); } return GraphicsJNI::createBitmap(env, nativeBitmap, getPremulBitmapCreateFlags(isMutable)); }
legacyBitmapConfigToColorType 將 Bitmap.Config.ARGB_8888 轉(zhuǎn)成skia域的顏色類(lèi)型 kBGRA_8888_SkColorType ,顏色類(lèi)型定義在 SkImageInfo.h 中, kARGB_4444_SkColorType 會(huì)強(qiáng)轉(zhuǎn)成 kN32_SkColorType ,它就是 kBGRA_8888_SkColorType ,不必糾結(jié)。
// /home/yuxiang/repo_aosp/android-6.0.1_r79/external/skia/include/core/SkImageInfo.h enum SkColorType { kUnknown_SkColorType, kAlpha_8_SkColorType, kRGB_565_SkColorType, kARGB_4444_SkColorType, kRGBA_8888_SkColorType, kBGRA_8888_SkColorType, kIndex_8_SkColorType, kGray_8_SkColorType, kLastEnum_SkColorType = kGray_8_SkColorType, #if SK_PMCOLOR_BYTE_ORDER(B,G,R,A) kN32_SkColorType = kBGRA_8888_SkColorType, #elif SK_PMCOLOR_BYTE_ORDER(R,G,B,A) kN32_SkColorType = kRGBA_8888_SkColorType, #else #error "SK_*32_SHFIT values must correspond to BGRA or RGBA byte order" #endif };
接著,根據(jù)寬、高、顏色類(lèi)型等創(chuàng)建 SkBitmap ,注意 kPremul_SkAlphaType 描述是 alpha 采用 premultiplied 處理的方式, CG 處理 alpha 存在 premultiplied和unpremultiplied兩 兩種方式。
public: SkImageInfo() : fWidth(0) , fHeight(0) , fColorType(kUnknown_SkColorType) , fAlphaType(kUnknown_SkAlphaType) , fProfileType(kLinear_SkColorProfileType) {} static SkImageInfo Make(int width, int height, SkColorType ct, SkAlphaType at, SkColorProfileType pt = kLinear_SkColorProfileType) { return SkImageInfo(width, height, ct, at, pt); }
Make 創(chuàng)建 SkImageInfo 對(duì)象, fWidth 的賦值是一個(gè)關(guān)鍵點(diǎn),后面Java層通過(guò) getAllocationByteCount 獲取 Bitmap 內(nèi)存占用中會(huì)用到它計(jì)算一行像素占用空間。 allocateJavaPixelRef 是通過(guò) JNI 調(diào)用 VMRuntime 實(shí)例的 newNonMovableArray 方法分配內(nèi)存。
int register_android_graphics_Graphics(JNIEnv* env) { jmethodID m; jclass c; ... gVMRuntime = env->NewGlobalRef(env->CallStaticObjectMethod(gVMRuntime_class, m)); gVMRuntime_newNonMovableArray = env->GetMethodID(gVMRuntime_class, "newNonMovableArray", "(Ljava/lang/Class;I)Ljava/lang/Object;"); ... } env->CallObjectMethod(gVMRuntime, gVMRuntime_newNonMovableArray, gByte_class, size) 拿到虛擬機(jī)分配Heap對(duì)象, env->CallLongMethod(gVMRuntime, gVMRuntime_addressOf, arrayObj) 拿到分配對(duì)象的地址,調(diào)用 native 層構(gòu)造函數(shù) new android::Bitmap(env, arrayObj, (void*) addr, info, rowBytes, ctable) // /home/yuxiang/repo_aosp/android-6.0.1_r79/frameworks/base/core/jni/android/graphics/Bitmap.cpp 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)); // Note: this will trigger a call to onStrongRefDestroyed(), but // we want the pixel ref to have a ref count of 0 at this point mPixelRef->unref(); } void Bitmap::getSkBitmap(SkBitmap* outBitmap) { assertValid(); android::AutoMutex _lock(mLock); // Safe because mPixelRef is a WrappedPixelRef type, otherwise rowBytes() // would require locking the pixels first. outBitmap->setInfo(mPixelRef->info(), mPixelRef->rowBytes()); outBitmap->setPixelRef(refPixelRefLocked())->unref(); outBitmap->setHasHardwareMipMap(hasHardwareMipMap()); } void Bitmap::pinPixelsLocked() { switch (mPixelStorageType) { case PixelStorageType::Invalid: LOG_ALWAYS_FATAL("Cannot pin invalid pixels!"); break; case PixelStorageType::External: case PixelStorageType::Ashmem: // Nothing to do break; case PixelStorageType::Java: { JNIEnv* env = jniEnv(); if (!mPixelStorage.java.jstrongRef) { mPixelStorage.java.jstrongRef = reinterpret_cast<jbyteArray>( env->NewGlobalRef(mPixelStorage.java.jweakRef)); if (!mPixelStorage.java.jstrongRef) { LOG_ALWAYS_FATAL("Failed to acquire strong reference to pixels"); } } break; } } } // /home/yuxiang/repo_aosp/android-6.0.1_r79/frameworks/base/core/jni/android/graphics/Bitmap.h std::unique_ptr<WrappedPixelRef> mPixelRef; PixelStorageType mPixelStorageType; union { struct { void* address; void* context; FreeFunc freeFunc; } external; struct { void* address; int fd; size_t size; } ashmem; struct { JavaVM* jvm; jweak jweakRef; jbyteArray jstrongRef; } java; } mPixelStorage;
native 層的 Bitmap 構(gòu)造函數(shù), mPixelStorage 保存前面創(chuàng)建 Heap 對(duì)象的弱引用, mPixelRef 指向 WrappedPixelRef 。 outBitmap 拿到 mPixelRef 強(qiáng)引用對(duì)象,這里理解為拿到 SkBitmap 對(duì)象。 Bitmap* nativeBitmap = GraphicsJNI::allocateJavaPixelRef 完成 Bitmap Heap 分配,創(chuàng)建 native 層 Bitmap , SkBitmap 對(duì)象,最后自然是創(chuàng)建 Java 層 Bitmap 對(duì)象,把該包的包上。 native 層是通過(guò) JNI 方法,在 Java 層創(chuàng)建一個(gè)數(shù)組對(duì)象的,這個(gè)數(shù)組是對(duì)應(yīng)在 Java 層的 Bitmap 對(duì)象的 buffer 數(shù)組,所以 pixels 還是保存在 Java 堆。而在 native 層這里它是通過(guò)weak指針來(lái)引用的,在需要的時(shí)候會(huì)轉(zhuǎn)換為strong指針,用完之后又去掉strong指針,這樣這個(gè)數(shù)組對(duì)象還是能夠被Java堆自動(dòng)回收。里面jstrongRef一開(kāi)始是賦值為null的,但是在bitmap的getSkBitmap方法會(huì)使用weakRef給他賦值。
// /home/yuxiang/repo_aosp/android-6.0.1_r79/frameworks/base/core/jni/android/graphics/Bitmap.cpp jobject GraphicsJNI::createBitmap(JNIEnv* env, android::Bitmap* bitmap, int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets, int density) { bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable; bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied; // The caller needs to have already set the alpha type properly, so the // native SkBitmap stays in sync with the Java Bitmap. assert_premultiplied(bitmap->info(), isPremultiplied); jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID, reinterpret_cast<jlong>(bitmap), bitmap->javaByteArray(), bitmap->width(), bitmap->height(), density, isMutable, isPremultiplied, ninePatchChunk, ninePatchInsets); hasException(env); // For the side effect of logging. return obj; }
重點(diǎn)看下這里 env->NewObject(gBitmap_class, gBitmap_constructorMethodID,... ,參數(shù)中有一處 bitmap->javaByteArray() ,指向的是Heap對(duì)象。所以,實(shí)際的像素內(nèi)存只有一份,被不同對(duì)象持有, Java 層的 Bitmap , native 層的 Btimap 。
這里順帶說(shuō)一下 JNI 生命周期。 JNI Local Reference 的生命期是在 native method 的執(zhí)行期(從 Java 程序切換到 native code 環(huán)境時(shí)開(kāi)始創(chuàng)建,或者在 native method 執(zhí)行時(shí)調(diào)用 JNI function 創(chuàng)建),在 native method 執(zhí)行完畢切換回 Java 程序時(shí),所有 JNI Local Reference 被刪除,生命期結(jié)束(調(diào)用 JNI function 可以提前結(jié)束其生命期)。
JNI 編程中明顯的內(nèi)存泄漏
Native Code 本身的內(nèi)存泄漏
JNI 編程首先是一門(mén)具體的編程語(yǔ)言,或者 C 語(yǔ)言,或者 C++,或者匯編,或者其它 native 的編程語(yǔ)言。每門(mén)編程語(yǔ)言環(huán)境都實(shí)現(xiàn)了自身的內(nèi)存管理機(jī)制。因此,JNI 程序開(kāi)發(fā)者要遵循 native 語(yǔ)言本身的內(nèi)存管理機(jī)制,避免造成內(nèi)存泄漏。以 C 語(yǔ)言為例,當(dāng)用 malloc() 在進(jìn)程堆中動(dòng)態(tài)分配內(nèi)存時(shí),JNI 程序在使用完后,應(yīng)當(dāng)調(diào)用 free() 將內(nèi)存釋放。總之,所有在 native 語(yǔ)言編程中應(yīng)當(dāng)注意的內(nèi)存泄漏規(guī)則,在 JNI 編程中依然適應(yīng)。
Native 語(yǔ)言本身引入的內(nèi)存泄漏會(huì)造成 native memory 的內(nèi)存,嚴(yán)重情況下會(huì)造成 native memory 的 out of memory。
Global Reference 引入的內(nèi)存泄漏
JNI 編程還要同時(shí)遵循 JNI 的規(guī)范標(biāo)準(zhǔn),JVM 附加了 JNI 編程特有的內(nèi)存管理機(jī)制。
JNI 中的 Local Reference 只在 native method 執(zhí)行時(shí)存在,當(dāng) native method 執(zhí)行完后自動(dòng)失效。這種自動(dòng)失效,使得對(duì) Local Reference 的使用相對(duì)簡(jiǎn)單,native method 執(zhí)行完后,它們所引用的 Java 對(duì)象的 reference count 會(huì)相應(yīng)減 1。不會(huì)造成 Java Heap 中 Java 對(duì)象的內(nèi)存泄漏。
而 Global Reference 對(duì) Java 對(duì)象的引用一直有效,因此它們引用的 Java 對(duì)象會(huì)一直存在 Java Heap 中。程序員在使用 Global Reference 時(shí),需要仔細(xì)維護(hù)對(duì) Global Reference 的使用。如果一定要使用 Global Reference,務(wù)必確保在不用的時(shí)候刪除。就像在 C 語(yǔ)言中,調(diào)用 malloc() 動(dòng)態(tài)分配一塊內(nèi)存之后,調(diào)用 free() 釋放一樣。否則,Global Reference 引用的 Java 對(duì)象將永遠(yuǎn)停留在 Java Heap 中,造成 Java Heap 的內(nèi)存泄漏。
更多JNI泄露,參考閱讀 JNI 編程中潛在的內(nèi)存泄漏——對(duì) LocalReference 的深入理解
Bitmap對(duì)象釋放
基于前文 JNI Local Reference和Global Reference 泄露,可以看到 nativeRecycle 實(shí)際調(diào)用native層Bitmap的 freePixels 方法, DeleteWeakGlobalRef 釋放Bitmap native層Gloabl引用。邏輯還是很簡(jiǎn)單的。
// /home/yuxiang/repo_aosp/android-6.0.1_r79/frameworks/base/core/jni/android/graphics/Bitmap.cpp void Bitmap::freePixels() { AutoMutex _lock(mLock); if (mPinnedRefCount == 0) { doFreePixels(); mPixelStorageType = PixelStorageType::Invalid; } } void Bitmap::doFreePixels() { switch (mPixelStorageType) { case PixelStorageType::Invalid: // already free'd, nothing to do break; case PixelStorageType::External: mPixelStorage.external.freeFunc(mPixelStorage.external.address, mPixelStorage.external.context); break; case PixelStorageType::Ashmem: munmap(mPixelStorage.ashmem.address, mPixelStorage.ashmem.size); close(mPixelStorage.ashmem.fd); break; case PixelStorageType::Java: JNIEnv* env = jniEnv(); LOG_ALWAYS_FATAL_IF(mPixelStorage.java.jstrongRef, "Deleting a bitmap wrapper while there are outstanding strong " "references! mPinnedRefCount = %d", mPinnedRefCount); env->DeleteWeakGlobalRef(mPixelStorage.java.jweakRef); break; } if (android::uirenderer::Caches::hasInstance()) { android::uirenderer::Caches::getInstance().textureCache.releaseTexture( mPixelRef->getStableID()); } }
需要注意兩點(diǎn)訊息,一是Java層主動(dòng)call recycle()方法或者Bitmap析構(gòu)函數(shù)都會(huì)調(diào)用freePixels,移除Global對(duì)象引用,這個(gè)對(duì)象是Heap上存一堆像素的空間。GC時(shí)釋放掉。二是,JNI不再持有Global Reference,并native函數(shù)執(zhí)行后釋放掉,但Java層的Bitmap對(duì)象還在,只是它的 mBuffer 和 mNativePtr 是無(wú)效地址,沒(méi)有像素Heap的Bitmap也就幾乎不消耗內(nèi)存了。至于Java層Bitmap對(duì)象什么時(shí)候釋放,生命周期結(jié)束自然free掉了。
// /home/yuxiang/repo_aosp/android-6.0.1_r79/art/runtime/jni_internal.cc static void DeleteWeakGlobalRef(JNIEnv* env, jweak obj) { JavaVMExt* vm = down_cast<JNIEnvExt*>(env)->vm; Thread* self = down_cast<JNIEnvExt*>(env)->self; vm->DeleteWeakGlobalRef(self, obj); } // /home/yuxiang/repo_aosp/android-6.0.1_r79/art/runtime/java_vm_ext.cc void JavaVMExt::DeleteWeakGlobalRef(Thread* self, jweak obj) { if (obj == nullptr) { return; } MutexLock mu(self, weak_globals_lock_); if (!weak_globals_.Remove(IRT_FIRST_SEGMENT, obj)) { LOG(WARNING) << "JNI WARNING: DeleteWeakGlobalRef(" << obj << ") " << "failed to find entry"; } }
通過(guò)BitmapFactory創(chuàng)建Bitmap
Bitmap工廠類(lèi)提供了多種decodeXXX方法創(chuàng)建Bitmap對(duì)象,主要是兼容不同的數(shù)據(jù)源,包括byte數(shù)組、文件、FD、Resource對(duì)象、InputStream,最終去到native層方法, 如下:
// BitmapFactory.java private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage, Rect padding, Options opts); private static native Bitmap nativeDecodeFileDescriptor(FileDescriptor fd, Rect padding, Options opts); private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts); private static native Bitmap nativeDecodeByteArray(byte[] data, int offset, int length, Options opts); private static native boolean nativeIsSeekable(FileDescriptor fd);
來(lái)看看 nativeDecodeStream 方法,該方法中先是創(chuàng)建了 bufferedStream 對(duì)象,接著 doDecode 返回Bitmap對(duì)象。 SkStreamRewindable 定義在 skia 庫(kù)中繼承 SkStream ,它聲明了兩個(gè)方法 rewind 和 duplicate ,寫(xiě)過(guò)網(wǎng)絡(luò)庫(kù)的同學(xué)一看命名便知是byte操作,前者功能是將文件內(nèi)部的指針重新指向一個(gè)流的開(kāi)頭,后者是創(chuàng)建共享此緩沖區(qū)內(nèi)容的新的字節(jié)緩沖區(qū)。
// BitmapFactory.cpp 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)); SkASSERT(bufferedStream.get() != NULL); bitmap = doDecode(env, bufferedStream, padding, options); } return bitmap; }
doDecode 先是通過(guò)JNI拿到 Java 層 Options 對(duì)象里面的屬性, outWidth、outHeight、inDensity、inTargetDensity 這些。后兩者用來(lái)計(jì)算Bitmap縮放比例,計(jì)算公式 scale = (float) targetDensity / density 。
// BitmapFactory.cpp 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; jobject javaBitmap = NULL; if (options != NULL) { sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID); if (optionsJustBounds(env, options)) { decodeMode = SkImageDecoder::kDecodeBounds_Mode; } // initialize these, in case we fail later on env->SetIntField(options, gOptions_widthFieldID, -1); env->SetIntField(options, gOptions_heightFieldID, -1); env->SetObjectField(options, gOptions_mimeFieldID, 0); jobject jconfig = env->GetObjectField(options, gOptions_configFieldID); prefColorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig); isMutable = env->GetBooleanField(options, gOptions_mutableFieldID); doDither = env->GetBooleanField(options, gOptions_ditherFieldID); preferQualityOverSpeed = env->GetBooleanField(options, gOptions_preferQualityOverSpeedFieldID); requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID); javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID); if (env->GetBooleanField(options, gOptions_scaledFieldID)) { const int density = env->GetIntField(options, gOptions_densityFieldID); const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID); const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID); if (density != 0 && targetDensity != 0 && density != screenDensity) { scale = (float) targetDensity / density; } } ... }
這些參數(shù)是提供給圖片解碼器 SkImageDecoder 。圖片資源無(wú)非是壓縮格式, SkImageDecoder 工廠類(lèi)根據(jù)輸入流同步拿到具體壓縮格式并創(chuàng)建相應(yīng)解碼器。 GetFormatName 返回支持的圖片格式。
SkImageDecoder 實(shí)例將 Options 參數(shù)設(shè)置下去。如此解壓出來(lái)的是根據(jù)實(shí)際尺寸裁剪后的圖片。
// 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_FactoryDefault.cpp SkImageDecoder* SkImageDecoder::Factory(SkStreamRewindable* stream) { return image_decoder_from_stream(stream); } // SkImageDecoder_FactoryRegistrar.cpp SkImageDecoder* image_decoder_from_stream(SkStreamRewindable* stream) { SkImageDecoder* codec = NULL; const SkImageDecoder_DecodeReg* curr = SkImageDecoder_DecodeReg::Head(); while (curr) { codec = curr->factory()(stream); // we rewind here, because we promise later when we call "decode", that // the stream will be at its beginning. bool rewindSuceeded = stream->rewind(); // our image decoder's require that rewind is supported so we fail early // if we are given a stream that does not support rewinding. if (!rewindSuceeded) { SkDEBUGF(("Unable to rewind the image stream.")); SkDELETE(codec); return NULL; } if (codec) { return codec; } curr = curr->next(); } return NULL; } // SkImageDecoder.cpp const char* SkImageDecoder::GetFormatName(Format format) { switch (format) { case kUnknown_Format: return "Unknown Format"; case kBMP_Format: return "BMP"; case kGIF_Format: return "GIF"; case kICO_Format: return "ICO"; case kPKM_Format: return "PKM"; case kKTX_Format: return "KTX"; case kASTC_Format: return "ASTC"; case kJPEG_Format: return "JPEG"; case kPNG_Format: return "PNG"; case kWBMP_Format: return "WBMP"; case kWEBP_Format: return "WEBP"; default: SkDEBUGFAIL("Invalid format type!"); } return "Unknown Format"; }
解碼僅僅完成數(shù)據(jù)的讀取,圖片是經(jīng)過(guò)渲染才能呈現(xiàn)在最終屏幕上,這個(gè)步驟在 canvas.drawBitmap 方法中完成。
// BitmapFactory.cpp static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) { ... SkBitmap outputBitmap; if (willScale) { // This is weird so let me explain: we could use the scale parameter // directly, but for historical reasons this is how the corresponding // Dalvik code has always behaved. We simply recreate the behavior here. // The result is slightly different from simply using scale because of // the 0.5f rounding bias applied when computing the target image size const float sx = scaledWidth / float(decodingBitmap.width()); const float sy = scaledHeight / float(decodingBitmap.height()); // TODO: avoid copying when scaled size equals decodingBitmap size SkColorType colorType = colorTypeForScaledOutput(decodingBitmap.colorType()); // FIXME: If the alphaType is kUnpremul and the image has alpha, the // colors may not be correct, since Skia does not yet support drawing // to/from unpremultiplied bitmaps. outputBitmap.setInfo(SkImageInfo::Make(scaledWidth, scaledHeight, colorType, decodingBitmap.alphaType())); if (!outputBitmap.tryAllocPixels(outputAllocator, NULL)) { return nullObjectReturn("allocation failed for scaled bitmap"); } // If outputBitmap's pixels are newly allocated by Java, there is no need // to erase to 0, since the pixels were initialized to 0. 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); } ... // now create the java bitmap return GraphicsJNI::createBitmap(env, javaAllocator.getStorageObjAndReset(), bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1); }
最終渲染后的圖片數(shù)據(jù)包在了 Bitmap 對(duì)象中,這部分邏輯重回第一章節(jié) Bitmap對(duì)象創(chuàng)建 。
相關(guān)文章
Kotlin協(xié)程之Flow觸發(fā)與消費(fèi)示例解析
Kotlin協(xié)程中,當(dāng)需要消費(fèi)流時(shí),會(huì)調(diào)用collect方法,觸發(fā)流的消費(fèi),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-09-09基于android實(shí)現(xiàn)五子棋開(kāi)發(fā)
這篇文章主要為大家詳細(xì)介紹了基于android實(shí)現(xiàn)五子棋開(kāi)發(fā),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-02-02Android菜單的定義及ActionBar的實(shí)現(xiàn)
本篇文章主要介紹了Android菜單的定義及ActionBar的實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08Android通過(guò)json向MySQL中讀寫(xiě)數(shù)據(jù)的方法詳解【寫(xiě)入篇】
這篇文章主要介紹了Android通過(guò)json向MySQL中讀寫(xiě)數(shù)據(jù)的方法,結(jié)合實(shí)例形式較為詳細(xì)的分析了Android json類(lèi)的定義、調(diào)用及php接收json數(shù)據(jù)并寫(xiě)入mysql的實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-06-06android webview 中l(wèi)ocalStorage無(wú)效的解決方法
這篇文章主要介紹了android webview 中l(wèi)ocalStorage無(wú)效的解決方法,本文直接給出解決方法實(shí)現(xiàn)代碼,需要的朋友可以參考下2015-06-06Kotlin標(biāo)準(zhǔn)函數(shù)與靜態(tài)方法基礎(chǔ)知識(shí)詳解
Kotlin中的標(biāo)準(zhǔn)函數(shù)指的是Standard.kt文件中定義的函數(shù),任何Kotlin代碼都可以自由地調(diào)用所有的標(biāo)準(zhǔn)函數(shù)。例如let這個(gè)標(biāo)準(zhǔn)函數(shù),他的主要作用就是配合?.操作符來(lái)進(jìn)行輔助判空處理2022-11-11Android Studio3.6設(shè)置Gradle Offline Mode的方法
這篇文章主要介紹了Android Studio3.6設(shè)置Gradle Offline Mode的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03Android實(shí)現(xiàn)旋轉(zhuǎn),放大,縮小圖片的方法
這篇文章主要介紹了Android實(shí)現(xiàn)旋轉(zhuǎn),放大,縮小圖片的方法,結(jié)合實(shí)例形式分析了Android基于Drawable針對(duì)圖片的縮放與旋轉(zhuǎn)等處理技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-10-10