diff options
| -rw-r--r-- | core/jni/Android.mk | 3 | ||||
| -rw-r--r-- | core/jni/android/graphics/BitmapRegionDecoder.cpp | 217 | ||||
| -rw-r--r-- | core/jni/android/graphics/Graphics.cpp | 87 | ||||
| -rw-r--r-- | core/jni/android/graphics/GraphicsJNI.h | 75 |
4 files changed, 268 insertions, 114 deletions
diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 4d648cea4675..476d13c5f024 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -198,7 +198,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) \ @@ -237,7 +236,7 @@ LOCAL_SHARED_LIBRARIES := \ libicuuc \ libicui18n \ libmedia \ - libjpeg \ + libjpeg-turbo \ libusbhost \ libharfbuzz_ng \ libz \ diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp index 3cdf640ae941..f10f4bda3226 100644 --- a/core/jni/android/graphics/BitmapRegionDecoder.cpp +++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp @@ -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" @@ -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); } - - SkImageDecoder* getDecoder() const { return fDecoder; } - int getWidth() const { return fWidth; } - int getHeight() const { return fHeight; } - -private: - SkImageDecoder* fDecoder; - int fWidth; - int fHeight; -}; + return jstr; +} // 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"); - } - - 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"); + return nullObjectReturn("CreateBitmapRegionDecoder returned null"); } - 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, @@ -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; } diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index ed4401978f7b..3d5091a78687 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -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); @@ -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"); diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index 90f8291fe846..e99a3ffa2a47 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -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" @@ -12,7 +14,7 @@ #include <Canvas.h> #include <jni.h> -class BitmapRegionDecoder; +class SkBitmapRegionDecoder; class SkCanvas; namespace android { @@ -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); @@ -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(); @@ -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); |