summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/jni/Android.mk3
-rw-r--r--core/jni/android/graphics/BitmapRegionDecoder.cpp217
-rw-r--r--core/jni/android/graphics/Graphics.cpp87
-rw-r--r--core/jni/android/graphics/GraphicsJNI.h75
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);