Loading core/jni/Android.mk +1 −2 Original line number Diff line number Diff line Loading @@ -199,7 +199,6 @@ LOCAL_C_INCLUDES += \ external/sqlite/android \ external/expat/lib \ external/tremor/Tremor \ external/jpeg \ external/harfbuzz_ng/src \ libcore/include \ $(call include-path-for, audio-utils) \ Loading Loading @@ -238,7 +237,7 @@ LOCAL_SHARED_LIBRARIES := \ libicuuc \ libicui18n \ libmedia \ libjpeg \ libjpeg-turbo \ libusbhost \ libharfbuzz_ng \ libz \ Loading core/jni/android/graphics/BitmapRegionDecoder.cpp +109 −108 Original line number Diff line number Diff line Loading @@ -16,17 +16,20 @@ #define LOG_TAG "BitmapRegionDecoder" #include "AutoDecodeCancel.h" #include "BitmapFactory.h" #include "CreateJavaOutputStreamAdaptor.h" #include "GraphicsJNI.h" #include "Utils.h" #include "SkBitmap.h" #include "SkBitmapRegionDecoder.h" #include "SkCodec.h" #include "SkData.h" #include "GraphicsJNI.h" #include "SkImageEncoder.h" #include "SkEncodedFormat.h" #include "SkUtils.h" #include "SkPixelRef.h" #include "SkStream.h" #include "Utils.h" #include "android_nio_utils.h" #include "android_util_Binder.h" #include "core_jni_helpers.h" Loading @@ -39,60 +42,54 @@ using namespace android; class BitmapRegionDecoder { public: BitmapRegionDecoder(SkImageDecoder* decoder, int width, int height) { fDecoder = decoder; fWidth = width; fHeight = height; } ~BitmapRegionDecoder() { delete fDecoder; // This is very similar to, and based on, getMimeTypeString() in BitmapFactory. jstring encodedFormatToString(JNIEnv* env, SkEncodedFormat format) { const char* mimeType; switch (format) { case SkEncodedFormat::kBMP_SkEncodedFormat: mimeType = "image/bmp"; break; case SkEncodedFormat::kGIF_SkEncodedFormat: mimeType = "image/gif"; break; case SkEncodedFormat::kICO_SkEncodedFormat: mimeType = "image/x-ico"; break; case SkEncodedFormat::kJPEG_SkEncodedFormat: mimeType = "image/jpeg"; break; case SkEncodedFormat::kPNG_SkEncodedFormat: mimeType = "image/png"; break; case SkEncodedFormat::kWEBP_SkEncodedFormat: mimeType = "image/webp"; break; case SkEncodedFormat::kWBMP_SkEncodedFormat: mimeType = "image/vnd.wap.wbmp"; break; default: mimeType = nullptr; break; } bool decodeRegion(SkBitmap* bitmap, const SkIRect& rect, SkColorType pref, int sampleSize) { fDecoder->setSampleSize(sampleSize); return fDecoder->decodeSubset(bitmap, rect, pref); jstring jstr = nullptr; if (mimeType != nullptr) { jstr = env->NewStringUTF(mimeType); } return jstr; } SkImageDecoder* getDecoder() const { return fDecoder; } int getWidth() const { return fWidth; } int getHeight() const { return fHeight; } private: SkImageDecoder* fDecoder; int fWidth; int fHeight; }; // Takes ownership of the SkStreamRewindable. For consistency, deletes stream even // when returning null. static jobject createBitmapRegionDecoder(JNIEnv* env, SkStreamRewindable* stream) { SkImageDecoder* decoder = SkImageDecoder::Factory(stream); int width, height; if (NULL == decoder) { delete stream; SkAutoTDelete<SkBitmapRegionDecoder> brd( SkBitmapRegionDecoder::Create(stream, SkBitmapRegionDecoder::kAndroidCodec_Strategy)); if (NULL == brd) { doThrowIOE(env, "Image format not supported"); return nullObjectReturn("SkImageDecoder::Factory returned null"); return nullObjectReturn("CreateBitmapRegionDecoder returned null"); } JavaPixelAllocator *javaAllocator = new JavaPixelAllocator(env); decoder->setAllocator(javaAllocator); javaAllocator->unref(); // This call passes ownership of stream to the decoder, or deletes on failure. if (!decoder->buildTileIndex(stream, &width, &height)) { char msg[100]; snprintf(msg, sizeof(msg), "Image failed to decode using %s decoder", decoder->getFormatName()); doThrowIOE(env, msg); delete decoder; return nullObjectReturn("decoder->buildTileIndex returned false"); } BitmapRegionDecoder *bm = new BitmapRegionDecoder(decoder, width, height); return GraphicsJNI::createBitmapRegionDecoder(env, bm); return GraphicsJNI::createBitmapRegionDecoder(env, brd.detach()); } static jobject nativeNewInstanceFromByteArray(JNIEnv* env, jobject, jbyteArray byteArray, Loading Loading @@ -160,102 +157,106 @@ static jobject nativeNewInstanceFromAsset(JNIEnv* env, jobject clazz, /* * nine patch not supported * * purgeable not supported * reportSizeToVM not supported */ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint start_x, jint start_y, jint width, jint height, jobject options) { BitmapRegionDecoder *brd = reinterpret_cast<BitmapRegionDecoder*>(brdHandle); jobject tileBitmap = NULL; SkImageDecoder *decoder = brd->getDecoder(); static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint inputX, jint inputY, jint inputWidth, jint inputHeight, jobject options) { // Set default options. int sampleSize = 1; SkColorType prefColorType = kUnknown_SkColorType; bool doDither = true; bool preferQualityOverSpeed = false; bool requireUnpremultiplied = false; SkColorType colorType = kN32_SkColorType; bool requireUnpremul = false; jobject javaBitmap = NULL; // Update the default options with any options supplied by the client. if (NULL != options) { sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID); // initialize these, in case we fail later on jobject jconfig = env->GetObjectField(options, gOptions_configFieldID); colorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig); if (kAlpha_8_SkColorType == colorType) { colorType = kGray_8_SkColorType; } requireUnpremul = !env->GetBooleanField(options, gOptions_premultipliedFieldID); javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID); // The Java options of ditherMode and preferQualityOverSpeed are deprecated. We will // ignore the values of these fields. // Initialize these fields to indicate a failure. If the decode succeeds, we // will update them 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); doDither = env->GetBooleanField(options, gOptions_ditherFieldID); preferQualityOverSpeed = env->GetBooleanField(options, gOptions_preferQualityOverSpeedFieldID); // Get the bitmap for re-use if it exists. tileBitmap = env->GetObjectField(options, gOptions_bitmapFieldID); requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID); } decoder->setDitherImage(doDither); decoder->setPreferQualityOverSpeed(preferQualityOverSpeed); decoder->setRequireUnpremultipliedColors(requireUnpremultiplied); AutoDecoderCancel adc(options, decoder); // To fix the race condition in case "requestCancelDecode" // happens earlier than AutoDecoderCancel object is added // to the gAutoDecoderCancelMutex linked list. if (NULL != options && env->GetBooleanField(options, gOptions_mCancelID)) { return nullObjectReturn("gOptions_mCancelID");; // Recycle a bitmap if possible. android::Bitmap* recycledBitmap = nullptr; size_t recycledBytes = 0; if (javaBitmap) { recycledBitmap = GraphicsJNI::getBitmap(env, javaBitmap); if (recycledBitmap->peekAtPixelRef()->isImmutable()) { ALOGW("Warning: Reusing an immutable bitmap as an image decoder target."); } recycledBytes = GraphicsJNI::getBitmapAllocationByteCount(env, javaBitmap); } SkIRect region; region.fLeft = start_x; region.fTop = start_y; region.fRight = start_x + width; region.fBottom = start_y + height; SkBitmap bitmap; if (tileBitmap != NULL) { // Re-use bitmap. GraphicsJNI::getSkBitmap(env, tileBitmap, &bitmap); // Set up the pixel allocator SkBRDAllocator* allocator = nullptr; RecyclingClippingPixelAllocator recycleAlloc(recycledBitmap, recycledBytes); JavaPixelAllocator javaAlloc(env); if (javaBitmap) { allocator = &recycleAlloc; // We are required to match the color type of the recycled bitmap. colorType = recycledBitmap->info().colorType(); } else { allocator = &javaAlloc; } if (!brd->decodeRegion(&bitmap, region, prefColorType, sampleSize)) { return nullObjectReturn("decoder->decodeRegion returned false"); // Decode the region. SkIRect subset = SkIRect::MakeXYWH(inputX, inputY, inputWidth, inputHeight); SkBitmapRegionDecoder* brd = reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle); SkBitmap bitmap; if (!brd->decodeRegion(&bitmap, allocator, subset, sampleSize, colorType, requireUnpremul)) { return nullObjectReturn("Failed to decode region."); } // update options (if any) // If the client provided options, indicate that the decode was successful. if (NULL != options) { env->SetIntField(options, gOptions_widthFieldID, bitmap.width()); env->SetIntField(options, gOptions_heightFieldID, bitmap.height()); // TODO: set the mimeType field with the data from the codec. // but how to reuse a set of strings, rather than allocating new one // each time? env->SetObjectField(options, gOptions_mimeFieldID, getMimeTypeString(env, decoder->getFormat())); encodedFormatToString(env, brd->getEncodedFormat())); } if (tileBitmap != NULL) { bitmap.notifyPixelsChanged(); return tileBitmap; // If we may have reused a bitmap, we need to indicate that the pixels have changed. if (javaBitmap) { recycleAlloc.copyIfNecessary(); return javaBitmap; } JavaPixelAllocator* allocator = (JavaPixelAllocator*) decoder->getAllocator(); int bitmapCreateFlags = 0; if (!requireUnpremultiplied) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Premultiplied; return GraphicsJNI::createBitmap(env, allocator->getStorageObjAndReset(), bitmapCreateFlags); if (!requireUnpremul) { bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Premultiplied; } return GraphicsJNI::createBitmap(env, javaAlloc.getStorageObjAndReset(), bitmapCreateFlags); } static jint nativeGetHeight(JNIEnv* env, jobject, jlong brdHandle) { BitmapRegionDecoder *brd = reinterpret_cast<BitmapRegionDecoder*>(brdHandle); return static_cast<jint>(brd->getHeight()); SkBitmapRegionDecoder* brd = reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle); return static_cast<jint>(brd->height()); } static jint nativeGetWidth(JNIEnv* env, jobject, jlong brdHandle) { BitmapRegionDecoder *brd = reinterpret_cast<BitmapRegionDecoder*>(brdHandle); return static_cast<jint>(brd->getWidth()); SkBitmapRegionDecoder* brd = reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle); return static_cast<jint>(brd->width()); } static void nativeClean(JNIEnv* env, jobject, jlong brdHandle) { BitmapRegionDecoder *brd = reinterpret_cast<BitmapRegionDecoder*>(brdHandle); SkBitmapRegionDecoder* brd = reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle); delete brd; } Loading core/jni/android/graphics/Graphics.cpp +86 −1 Original line number Diff line number Diff line Loading @@ -439,7 +439,7 @@ int GraphicsJNI::getBitmapAllocationByteCount(JNIEnv* env, jobject javaBitmap) return env->CallIntMethod(javaBitmap, gBitmap_getAllocationByteCountMethodID); } jobject GraphicsJNI::createBitmapRegionDecoder(JNIEnv* env, BitmapRegionDecoder* bitmap) jobject GraphicsJNI::createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap) { SkASSERT(bitmap != NULL); Loading Loading @@ -677,6 +677,91 @@ bool JavaPixelAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) { //////////////////////////////////////////////////////////////////////////////// RecyclingClippingPixelAllocator::RecyclingClippingPixelAllocator( android::Bitmap* recycledBitmap, size_t recycledBytes) : mRecycledBitmap(recycledBitmap) , mRecycledBytes(recycledBytes) , mSkiaBitmap(nullptr) , mNeedsCopy(false) {} RecyclingClippingPixelAllocator::~RecyclingClippingPixelAllocator() {} bool RecyclingClippingPixelAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) { // Ensure that the caller did not pass in a NULL bitmap to the constructor or this // function. LOG_ALWAYS_FATAL_IF(!mRecycledBitmap); LOG_ALWAYS_FATAL_IF(!bitmap); mSkiaBitmap = bitmap; // This behaves differently than the RecyclingPixelAllocator. For backwards // compatibility, the original color type of the recycled bitmap must be maintained. if (mRecycledBitmap->info().colorType() != bitmap->colorType()) { return false; } // The Skia bitmap specifies the width and height needed by the decoder. // mRecycledBitmap specifies the width and height of the bitmap that we // want to reuse. Neither can be changed. We will try to find a way // to reuse the memory. const int maxWidth = SkTMax(bitmap->width(), mRecycledBitmap->info().width()); const int maxHeight = SkTMax(bitmap->height(), mRecycledBitmap->info().height()); const SkImageInfo maxInfo = bitmap->info().makeWH(maxWidth, maxHeight); const size_t rowBytes = maxInfo.minRowBytes(); const size_t bytesNeeded = maxInfo.getSafeSize(rowBytes); if (bytesNeeded <= mRecycledBytes) { // Here we take advantage of reconfigure() to reset the rowBytes and ctable // of mRecycledBitmap. It is very important that we pass in // mRecycledBitmap->info() for the SkImageInfo. According to the // specification for BitmapRegionDecoder, we are not allowed to change // the SkImageInfo. mRecycledBitmap->reconfigure(mRecycledBitmap->info(), rowBytes, ctable); // This call will give the bitmap the same pixelRef as mRecycledBitmap. bitmap->setPixelRef(mRecycledBitmap->refPixelRef())->unref(); // Make sure that the recycled bitmap has the correct alpha type. mRecycledBitmap->setAlphaType(bitmap->alphaType()); bitmap->lockPixels(); mNeedsCopy = false; // TODO: If the dimensions of the SkBitmap are smaller than those of // mRecycledBitmap, should we zero the memory in mRecycledBitmap? return true; } // In the event that mRecycledBitmap is not large enough, allocate new memory // on the heap. SkBitmap::HeapAllocator heapAllocator; // We will need to copy from heap memory to mRecycledBitmap's memory after the // decode is complete. mNeedsCopy = true; return heapAllocator.allocPixelRef(bitmap, ctable); } void RecyclingClippingPixelAllocator::copyIfNecessary() { if (mNeedsCopy) { SkPixelRef* recycledPixels = mRecycledBitmap->refPixelRef(); void* dst = recycledPixels->pixels(); size_t dstRowBytes = mRecycledBitmap->rowBytes(); size_t bytesToCopy = SkTMin(mRecycledBitmap->info().minRowBytes(), mSkiaBitmap->info().minRowBytes()); for (int y = 0; y < mRecycledBitmap->info().height(); y++) { memcpy(dst, mSkiaBitmap->getAddr(0, y), bytesToCopy); dst = SkTAddOffset<void>(dst, dstRowBytes); } recycledPixels->notifyPixelsChanged(); recycledPixels->unref(); } mRecycledBitmap = nullptr; mSkiaBitmap = nullptr; } //////////////////////////////////////////////////////////////////////////////// AshmemPixelAllocator::AshmemPixelAllocator(JNIEnv *env) { LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&mJavaVM) != JNI_OK, "env->GetJavaVM failed"); Loading core/jni/android/graphics/GraphicsJNI.h +72 −3 Original line number Diff line number Diff line Loading @@ -3,6 +3,8 @@ #include "Bitmap.h" #include "SkBitmap.h" #include "SkBRDAllocator.h" #include "SkCodec.h" #include "SkDevice.h" #include "SkPixelRef.h" #include "SkMallocPixelRef.h" Loading @@ -12,7 +14,7 @@ #include <Canvas.h> #include <jni.h> class BitmapRegionDecoder; class SkBitmapRegionDecoder; class SkCanvas; namespace android { Loading Loading @@ -90,7 +92,7 @@ public: static jobject createRegion(JNIEnv* env, SkRegion* region); static jobject createBitmapRegionDecoder(JNIEnv* env, BitmapRegionDecoder* bitmap); static jobject createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap); static android::Bitmap* allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap, SkColorTable* ctable); Loading Loading @@ -123,7 +125,7 @@ public: * ensure that the allocated buffer is properly accounted for with a * reference in the heap (or a JNI global reference). */ class JavaPixelAllocator : public SkBitmap::Allocator { class JavaPixelAllocator : public SkBRDAllocator { public: JavaPixelAllocator(JNIEnv* env); ~JavaPixelAllocator(); Loading @@ -139,11 +141,78 @@ public: return result; }; /** * Indicates that this allocator allocates zero initialized * memory. */ SkCodec::ZeroInitialized zeroInit() const override { return SkCodec::kYes_ZeroInitialized; } private: JavaVM* mJavaVM; android::Bitmap* mStorage = nullptr; }; /** * Allocator to handle reusing bitmaps for BitmapRegionDecoder. * * The BitmapRegionDecoder documentation states that, if it is * provided, the recycled bitmap will always be reused, clipping * the decoded output to fit in the recycled bitmap if necessary. * This allocator implements that behavior. * * Skia's SkBitmapRegionDecoder expects the memory that * is allocated to be large enough to decode the entire region * that is requested. It will decode directly into the memory * that is provided. * * FIXME: BUG:25465958 * If the recycled bitmap is not large enough for the decode * requested, meaning that a clip is required, we will allocate * enough memory for Skia to perform the decode, and then copy * from the decoded output into the recycled bitmap. * * If the recycled bitmap is large enough for the decode requested, * we will provide that memory for Skia to decode directly into. * * This allocator should only be used for a single allocation. * After we reuse the recycledBitmap once, it is dangerous to * reuse it again, given that it still may be in use from our * first allocation. */ class RecyclingClippingPixelAllocator : public SkBRDAllocator { public: RecyclingClippingPixelAllocator(android::Bitmap* recycledBitmap, size_t recycledBytes); ~RecyclingClippingPixelAllocator(); virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) override; /** * Must be called! * * In the event that the recycled bitmap is not large enough for * the allocation requested, we will allocate memory on the heap * instead. As a final step, once we are done using this memory, * we will copy the contents of the heap memory into the recycled * bitmap's memory, clipping as necessary. */ void copyIfNecessary(); /** * Indicates that this allocator does not allocate zero initialized * memory. */ SkCodec::ZeroInitialized zeroInit() const override { return SkCodec::kNo_ZeroInitialized; } private: android::Bitmap* mRecycledBitmap; const size_t mRecycledBytes; SkBitmap* mSkiaBitmap; bool mNeedsCopy; }; class AshmemPixelAllocator : public SkBitmap::Allocator { public: AshmemPixelAllocator(JNIEnv* env); Loading Loading
core/jni/Android.mk +1 −2 Original line number Diff line number Diff line Loading @@ -199,7 +199,6 @@ LOCAL_C_INCLUDES += \ external/sqlite/android \ external/expat/lib \ external/tremor/Tremor \ external/jpeg \ external/harfbuzz_ng/src \ libcore/include \ $(call include-path-for, audio-utils) \ Loading Loading @@ -238,7 +237,7 @@ LOCAL_SHARED_LIBRARIES := \ libicuuc \ libicui18n \ libmedia \ libjpeg \ libjpeg-turbo \ libusbhost \ libharfbuzz_ng \ libz \ Loading
core/jni/android/graphics/BitmapRegionDecoder.cpp +109 −108 Original line number Diff line number Diff line Loading @@ -16,17 +16,20 @@ #define LOG_TAG "BitmapRegionDecoder" #include "AutoDecodeCancel.h" #include "BitmapFactory.h" #include "CreateJavaOutputStreamAdaptor.h" #include "GraphicsJNI.h" #include "Utils.h" #include "SkBitmap.h" #include "SkBitmapRegionDecoder.h" #include "SkCodec.h" #include "SkData.h" #include "GraphicsJNI.h" #include "SkImageEncoder.h" #include "SkEncodedFormat.h" #include "SkUtils.h" #include "SkPixelRef.h" #include "SkStream.h" #include "Utils.h" #include "android_nio_utils.h" #include "android_util_Binder.h" #include "core_jni_helpers.h" Loading @@ -39,60 +42,54 @@ using namespace android; class BitmapRegionDecoder { public: BitmapRegionDecoder(SkImageDecoder* decoder, int width, int height) { fDecoder = decoder; fWidth = width; fHeight = height; } ~BitmapRegionDecoder() { delete fDecoder; // This is very similar to, and based on, getMimeTypeString() in BitmapFactory. jstring encodedFormatToString(JNIEnv* env, SkEncodedFormat format) { const char* mimeType; switch (format) { case SkEncodedFormat::kBMP_SkEncodedFormat: mimeType = "image/bmp"; break; case SkEncodedFormat::kGIF_SkEncodedFormat: mimeType = "image/gif"; break; case SkEncodedFormat::kICO_SkEncodedFormat: mimeType = "image/x-ico"; break; case SkEncodedFormat::kJPEG_SkEncodedFormat: mimeType = "image/jpeg"; break; case SkEncodedFormat::kPNG_SkEncodedFormat: mimeType = "image/png"; break; case SkEncodedFormat::kWEBP_SkEncodedFormat: mimeType = "image/webp"; break; case SkEncodedFormat::kWBMP_SkEncodedFormat: mimeType = "image/vnd.wap.wbmp"; break; default: mimeType = nullptr; break; } bool decodeRegion(SkBitmap* bitmap, const SkIRect& rect, SkColorType pref, int sampleSize) { fDecoder->setSampleSize(sampleSize); return fDecoder->decodeSubset(bitmap, rect, pref); jstring jstr = nullptr; if (mimeType != nullptr) { jstr = env->NewStringUTF(mimeType); } return jstr; } SkImageDecoder* getDecoder() const { return fDecoder; } int getWidth() const { return fWidth; } int getHeight() const { return fHeight; } private: SkImageDecoder* fDecoder; int fWidth; int fHeight; }; // Takes ownership of the SkStreamRewindable. For consistency, deletes stream even // when returning null. static jobject createBitmapRegionDecoder(JNIEnv* env, SkStreamRewindable* stream) { SkImageDecoder* decoder = SkImageDecoder::Factory(stream); int width, height; if (NULL == decoder) { delete stream; SkAutoTDelete<SkBitmapRegionDecoder> brd( SkBitmapRegionDecoder::Create(stream, SkBitmapRegionDecoder::kAndroidCodec_Strategy)); if (NULL == brd) { doThrowIOE(env, "Image format not supported"); return nullObjectReturn("SkImageDecoder::Factory returned null"); return nullObjectReturn("CreateBitmapRegionDecoder returned null"); } JavaPixelAllocator *javaAllocator = new JavaPixelAllocator(env); decoder->setAllocator(javaAllocator); javaAllocator->unref(); // This call passes ownership of stream to the decoder, or deletes on failure. if (!decoder->buildTileIndex(stream, &width, &height)) { char msg[100]; snprintf(msg, sizeof(msg), "Image failed to decode using %s decoder", decoder->getFormatName()); doThrowIOE(env, msg); delete decoder; return nullObjectReturn("decoder->buildTileIndex returned false"); } BitmapRegionDecoder *bm = new BitmapRegionDecoder(decoder, width, height); return GraphicsJNI::createBitmapRegionDecoder(env, bm); return GraphicsJNI::createBitmapRegionDecoder(env, brd.detach()); } static jobject nativeNewInstanceFromByteArray(JNIEnv* env, jobject, jbyteArray byteArray, Loading Loading @@ -160,102 +157,106 @@ static jobject nativeNewInstanceFromAsset(JNIEnv* env, jobject clazz, /* * nine patch not supported * * purgeable not supported * reportSizeToVM not supported */ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint start_x, jint start_y, jint width, jint height, jobject options) { BitmapRegionDecoder *brd = reinterpret_cast<BitmapRegionDecoder*>(brdHandle); jobject tileBitmap = NULL; SkImageDecoder *decoder = brd->getDecoder(); static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint inputX, jint inputY, jint inputWidth, jint inputHeight, jobject options) { // Set default options. int sampleSize = 1; SkColorType prefColorType = kUnknown_SkColorType; bool doDither = true; bool preferQualityOverSpeed = false; bool requireUnpremultiplied = false; SkColorType colorType = kN32_SkColorType; bool requireUnpremul = false; jobject javaBitmap = NULL; // Update the default options with any options supplied by the client. if (NULL != options) { sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID); // initialize these, in case we fail later on jobject jconfig = env->GetObjectField(options, gOptions_configFieldID); colorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig); if (kAlpha_8_SkColorType == colorType) { colorType = kGray_8_SkColorType; } requireUnpremul = !env->GetBooleanField(options, gOptions_premultipliedFieldID); javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID); // The Java options of ditherMode and preferQualityOverSpeed are deprecated. We will // ignore the values of these fields. // Initialize these fields to indicate a failure. If the decode succeeds, we // will update them 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); doDither = env->GetBooleanField(options, gOptions_ditherFieldID); preferQualityOverSpeed = env->GetBooleanField(options, gOptions_preferQualityOverSpeedFieldID); // Get the bitmap for re-use if it exists. tileBitmap = env->GetObjectField(options, gOptions_bitmapFieldID); requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID); } decoder->setDitherImage(doDither); decoder->setPreferQualityOverSpeed(preferQualityOverSpeed); decoder->setRequireUnpremultipliedColors(requireUnpremultiplied); AutoDecoderCancel adc(options, decoder); // To fix the race condition in case "requestCancelDecode" // happens earlier than AutoDecoderCancel object is added // to the gAutoDecoderCancelMutex linked list. if (NULL != options && env->GetBooleanField(options, gOptions_mCancelID)) { return nullObjectReturn("gOptions_mCancelID");; // Recycle a bitmap if possible. android::Bitmap* recycledBitmap = nullptr; size_t recycledBytes = 0; if (javaBitmap) { recycledBitmap = GraphicsJNI::getBitmap(env, javaBitmap); if (recycledBitmap->peekAtPixelRef()->isImmutable()) { ALOGW("Warning: Reusing an immutable bitmap as an image decoder target."); } recycledBytes = GraphicsJNI::getBitmapAllocationByteCount(env, javaBitmap); } SkIRect region; region.fLeft = start_x; region.fTop = start_y; region.fRight = start_x + width; region.fBottom = start_y + height; SkBitmap bitmap; if (tileBitmap != NULL) { // Re-use bitmap. GraphicsJNI::getSkBitmap(env, tileBitmap, &bitmap); // Set up the pixel allocator SkBRDAllocator* allocator = nullptr; RecyclingClippingPixelAllocator recycleAlloc(recycledBitmap, recycledBytes); JavaPixelAllocator javaAlloc(env); if (javaBitmap) { allocator = &recycleAlloc; // We are required to match the color type of the recycled bitmap. colorType = recycledBitmap->info().colorType(); } else { allocator = &javaAlloc; } if (!brd->decodeRegion(&bitmap, region, prefColorType, sampleSize)) { return nullObjectReturn("decoder->decodeRegion returned false"); // Decode the region. SkIRect subset = SkIRect::MakeXYWH(inputX, inputY, inputWidth, inputHeight); SkBitmapRegionDecoder* brd = reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle); SkBitmap bitmap; if (!brd->decodeRegion(&bitmap, allocator, subset, sampleSize, colorType, requireUnpremul)) { return nullObjectReturn("Failed to decode region."); } // update options (if any) // If the client provided options, indicate that the decode was successful. if (NULL != options) { env->SetIntField(options, gOptions_widthFieldID, bitmap.width()); env->SetIntField(options, gOptions_heightFieldID, bitmap.height()); // TODO: set the mimeType field with the data from the codec. // but how to reuse a set of strings, rather than allocating new one // each time? env->SetObjectField(options, gOptions_mimeFieldID, getMimeTypeString(env, decoder->getFormat())); encodedFormatToString(env, brd->getEncodedFormat())); } if (tileBitmap != NULL) { bitmap.notifyPixelsChanged(); return tileBitmap; // If we may have reused a bitmap, we need to indicate that the pixels have changed. if (javaBitmap) { recycleAlloc.copyIfNecessary(); return javaBitmap; } JavaPixelAllocator* allocator = (JavaPixelAllocator*) decoder->getAllocator(); int bitmapCreateFlags = 0; if (!requireUnpremultiplied) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Premultiplied; return GraphicsJNI::createBitmap(env, allocator->getStorageObjAndReset(), bitmapCreateFlags); if (!requireUnpremul) { bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Premultiplied; } return GraphicsJNI::createBitmap(env, javaAlloc.getStorageObjAndReset(), bitmapCreateFlags); } static jint nativeGetHeight(JNIEnv* env, jobject, jlong brdHandle) { BitmapRegionDecoder *brd = reinterpret_cast<BitmapRegionDecoder*>(brdHandle); return static_cast<jint>(brd->getHeight()); SkBitmapRegionDecoder* brd = reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle); return static_cast<jint>(brd->height()); } static jint nativeGetWidth(JNIEnv* env, jobject, jlong brdHandle) { BitmapRegionDecoder *brd = reinterpret_cast<BitmapRegionDecoder*>(brdHandle); return static_cast<jint>(brd->getWidth()); SkBitmapRegionDecoder* brd = reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle); return static_cast<jint>(brd->width()); } static void nativeClean(JNIEnv* env, jobject, jlong brdHandle) { BitmapRegionDecoder *brd = reinterpret_cast<BitmapRegionDecoder*>(brdHandle); SkBitmapRegionDecoder* brd = reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle); delete brd; } Loading
core/jni/android/graphics/Graphics.cpp +86 −1 Original line number Diff line number Diff line Loading @@ -439,7 +439,7 @@ int GraphicsJNI::getBitmapAllocationByteCount(JNIEnv* env, jobject javaBitmap) return env->CallIntMethod(javaBitmap, gBitmap_getAllocationByteCountMethodID); } jobject GraphicsJNI::createBitmapRegionDecoder(JNIEnv* env, BitmapRegionDecoder* bitmap) jobject GraphicsJNI::createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap) { SkASSERT(bitmap != NULL); Loading Loading @@ -677,6 +677,91 @@ bool JavaPixelAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) { //////////////////////////////////////////////////////////////////////////////// RecyclingClippingPixelAllocator::RecyclingClippingPixelAllocator( android::Bitmap* recycledBitmap, size_t recycledBytes) : mRecycledBitmap(recycledBitmap) , mRecycledBytes(recycledBytes) , mSkiaBitmap(nullptr) , mNeedsCopy(false) {} RecyclingClippingPixelAllocator::~RecyclingClippingPixelAllocator() {} bool RecyclingClippingPixelAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) { // Ensure that the caller did not pass in a NULL bitmap to the constructor or this // function. LOG_ALWAYS_FATAL_IF(!mRecycledBitmap); LOG_ALWAYS_FATAL_IF(!bitmap); mSkiaBitmap = bitmap; // This behaves differently than the RecyclingPixelAllocator. For backwards // compatibility, the original color type of the recycled bitmap must be maintained. if (mRecycledBitmap->info().colorType() != bitmap->colorType()) { return false; } // The Skia bitmap specifies the width and height needed by the decoder. // mRecycledBitmap specifies the width and height of the bitmap that we // want to reuse. Neither can be changed. We will try to find a way // to reuse the memory. const int maxWidth = SkTMax(bitmap->width(), mRecycledBitmap->info().width()); const int maxHeight = SkTMax(bitmap->height(), mRecycledBitmap->info().height()); const SkImageInfo maxInfo = bitmap->info().makeWH(maxWidth, maxHeight); const size_t rowBytes = maxInfo.minRowBytes(); const size_t bytesNeeded = maxInfo.getSafeSize(rowBytes); if (bytesNeeded <= mRecycledBytes) { // Here we take advantage of reconfigure() to reset the rowBytes and ctable // of mRecycledBitmap. It is very important that we pass in // mRecycledBitmap->info() for the SkImageInfo. According to the // specification for BitmapRegionDecoder, we are not allowed to change // the SkImageInfo. mRecycledBitmap->reconfigure(mRecycledBitmap->info(), rowBytes, ctable); // This call will give the bitmap the same pixelRef as mRecycledBitmap. bitmap->setPixelRef(mRecycledBitmap->refPixelRef())->unref(); // Make sure that the recycled bitmap has the correct alpha type. mRecycledBitmap->setAlphaType(bitmap->alphaType()); bitmap->lockPixels(); mNeedsCopy = false; // TODO: If the dimensions of the SkBitmap are smaller than those of // mRecycledBitmap, should we zero the memory in mRecycledBitmap? return true; } // In the event that mRecycledBitmap is not large enough, allocate new memory // on the heap. SkBitmap::HeapAllocator heapAllocator; // We will need to copy from heap memory to mRecycledBitmap's memory after the // decode is complete. mNeedsCopy = true; return heapAllocator.allocPixelRef(bitmap, ctable); } void RecyclingClippingPixelAllocator::copyIfNecessary() { if (mNeedsCopy) { SkPixelRef* recycledPixels = mRecycledBitmap->refPixelRef(); void* dst = recycledPixels->pixels(); size_t dstRowBytes = mRecycledBitmap->rowBytes(); size_t bytesToCopy = SkTMin(mRecycledBitmap->info().minRowBytes(), mSkiaBitmap->info().minRowBytes()); for (int y = 0; y < mRecycledBitmap->info().height(); y++) { memcpy(dst, mSkiaBitmap->getAddr(0, y), bytesToCopy); dst = SkTAddOffset<void>(dst, dstRowBytes); } recycledPixels->notifyPixelsChanged(); recycledPixels->unref(); } mRecycledBitmap = nullptr; mSkiaBitmap = nullptr; } //////////////////////////////////////////////////////////////////////////////// AshmemPixelAllocator::AshmemPixelAllocator(JNIEnv *env) { LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&mJavaVM) != JNI_OK, "env->GetJavaVM failed"); Loading
core/jni/android/graphics/GraphicsJNI.h +72 −3 Original line number Diff line number Diff line Loading @@ -3,6 +3,8 @@ #include "Bitmap.h" #include "SkBitmap.h" #include "SkBRDAllocator.h" #include "SkCodec.h" #include "SkDevice.h" #include "SkPixelRef.h" #include "SkMallocPixelRef.h" Loading @@ -12,7 +14,7 @@ #include <Canvas.h> #include <jni.h> class BitmapRegionDecoder; class SkBitmapRegionDecoder; class SkCanvas; namespace android { Loading Loading @@ -90,7 +92,7 @@ public: static jobject createRegion(JNIEnv* env, SkRegion* region); static jobject createBitmapRegionDecoder(JNIEnv* env, BitmapRegionDecoder* bitmap); static jobject createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap); static android::Bitmap* allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap, SkColorTable* ctable); Loading Loading @@ -123,7 +125,7 @@ public: * ensure that the allocated buffer is properly accounted for with a * reference in the heap (or a JNI global reference). */ class JavaPixelAllocator : public SkBitmap::Allocator { class JavaPixelAllocator : public SkBRDAllocator { public: JavaPixelAllocator(JNIEnv* env); ~JavaPixelAllocator(); Loading @@ -139,11 +141,78 @@ public: return result; }; /** * Indicates that this allocator allocates zero initialized * memory. */ SkCodec::ZeroInitialized zeroInit() const override { return SkCodec::kYes_ZeroInitialized; } private: JavaVM* mJavaVM; android::Bitmap* mStorage = nullptr; }; /** * Allocator to handle reusing bitmaps for BitmapRegionDecoder. * * The BitmapRegionDecoder documentation states that, if it is * provided, the recycled bitmap will always be reused, clipping * the decoded output to fit in the recycled bitmap if necessary. * This allocator implements that behavior. * * Skia's SkBitmapRegionDecoder expects the memory that * is allocated to be large enough to decode the entire region * that is requested. It will decode directly into the memory * that is provided. * * FIXME: BUG:25465958 * If the recycled bitmap is not large enough for the decode * requested, meaning that a clip is required, we will allocate * enough memory for Skia to perform the decode, and then copy * from the decoded output into the recycled bitmap. * * If the recycled bitmap is large enough for the decode requested, * we will provide that memory for Skia to decode directly into. * * This allocator should only be used for a single allocation. * After we reuse the recycledBitmap once, it is dangerous to * reuse it again, given that it still may be in use from our * first allocation. */ class RecyclingClippingPixelAllocator : public SkBRDAllocator { public: RecyclingClippingPixelAllocator(android::Bitmap* recycledBitmap, size_t recycledBytes); ~RecyclingClippingPixelAllocator(); virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) override; /** * Must be called! * * In the event that the recycled bitmap is not large enough for * the allocation requested, we will allocate memory on the heap * instead. As a final step, once we are done using this memory, * we will copy the contents of the heap memory into the recycled * bitmap's memory, clipping as necessary. */ void copyIfNecessary(); /** * Indicates that this allocator does not allocate zero initialized * memory. */ SkCodec::ZeroInitialized zeroInit() const override { return SkCodec::kNo_ZeroInitialized; } private: android::Bitmap* mRecycledBitmap; const size_t mRecycledBytes; SkBitmap* mSkiaBitmap; bool mNeedsCopy; }; class AshmemPixelAllocator : public SkBitmap::Allocator { public: AshmemPixelAllocator(JNIEnv* env); Loading