diff options
author | Leon Scroggins III <scroggo@google.com> | 2013-08-20 17:59:39 -0400 |
---|---|---|
committer | Leon Scroggins III <scroggo@google.com> | 2013-08-27 15:28:58 -0400 |
commit | ca32021b43f326af7d3f4ae041f8db297f98a518 (patch) | |
tree | 95c04e5b699d34181a2fc2f4e866bc2759250fdb /core | |
parent | fead1290d74492af5fdf3dcb6739fbd1898dc239 (diff) | |
download | frameworks_base-ca32021b43f326af7d3f4ae041f8db297f98a518.zip frameworks_base-ca32021b43f326af7d3f4ae041f8db297f98a518.tar.gz frameworks_base-ca32021b43f326af7d3f4ae041f8db297f98a518.tar.bz2 |
Replace stream wrap-function w/ more specific ones
The current stream wrapper returns a potentially incorrect
value for a call to getLength(), is typically copied into
another stream (not always in the same way), and doesn't
always take advantage of its underlying data (like when it
is an Asset). The overall goal of this CL is to provide the
caller with something that is ready to use, depending on
what is asked for. If a copy is desired, the copy is made
before being returned to the caller.
core/jni/android/graphics/Bitmap.cpp:
Include SkStream.h, since it is no longer included by
CreateJavaOutputStreamAdaptor's header file.
core/jni/android/graphics/BitmapFactory.cpp:
Pass an SkStreamRewindable to decoding functions, as Skia
decoders will be updated to only take an SkStreamRewindable
(which makes more sense because they require rewinding).
Call the more specific GetRewindableStream to get a
rewindable stream.
Remove copyAssetToStream which has been moved to Utils.
In nativeDecodeAsset, pass forcePurgeable as allowPurgeable
in doDecode. Technically the old code worked, but it checked
the BitmapOptions again.
Remove getFDSize, which is no longer used.
core/jni/android/graphics/BitmapRegionDecoder.cpp:
Remove redundant buildSkMemoryStream. nativeNewInstanceFromStream
now calls CopyJavaInputStream, which handles the copy.
Copy the Asset directly, using common code, rather than creating
an AssetStreamAdaptor to copy.
core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp:
core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h:
Provide new interfaces to access data from a Java InputStream.
The new interfaces are more specific about what type of stream
is desired.
Use forward declarations where possible.
Remove doSize, which gives a misleading answer to the question
of how long the entire stream is.
TODO: Only call FindClass etc once.
core/jni/android/graphics/Movie.cpp:
Check for an asset stream, and use it if possible. Then call
GetRewindableStream if there is not an asset.
Remove the memory leak. Call DeleteLocalRef to delete the
allocated memory.
core/jni/android/graphics/Picture.cpp:
Call the new interface.
core/jni/android/graphics/Utils.cpp:
core/jni/android/graphics/Utils.h:
Make AssetStreamAdaptor inherit from SkStreamRewindable so it
can be passed to Skia decoding functions once they require it.
Add CopyAssetToStream (moved from BitmapFactory.cpp) so it can
be used by multiple files.
graphics/java/android/graphics/BitmapFactory.java:
Remove the call to mark, which is now done natively.
Remove the BufferedInputStream. Mark/reset is now handled
by native code.
Allow decodeStream to handle a FileInputStream by using the
FileDescriptor, if it is seekable. In decodeFileDescriptor,
call nativeDecodeStream instead of decodeStream so this new
functionality will not loop.
Call setDensityFromOptions in decodeFileDescriptor.
graphics/java/android/graphics/BitmapRegionDecoder.java:
Remove the BufferedInputStream. Mark/reset is now handled
by native code.
TODO: ADD TESTS!
Requires https://googleplex-android-review.googlesource.com/#/c/344317/
BUG=https://b.corp.google.com/issue?id=8432093
Change-Id: I4419b70b3482325c98ecc673dbfc4613f1b18581
Diffstat (limited to 'core')
-rw-r--r-- | core/jni/android/graphics/Bitmap.cpp | 1 | ||||
-rw-r--r-- | core/jni/android/graphics/BitmapFactory.cpp | 55 | ||||
-rw-r--r-- | core/jni/android/graphics/BitmapRegionDecoder.cpp | 43 | ||||
-rw-r--r-- | core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp | 276 | ||||
-rw-r--r-- | core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h | 66 | ||||
-rw-r--r-- | core/jni/android/graphics/Movie.cpp | 13 | ||||
-rw-r--r-- | core/jni/android/graphics/Picture.cpp | 8 | ||||
-rw-r--r-- | core/jni/android/graphics/Utils.cpp | 48 | ||||
-rw-r--r-- | core/jni/android/graphics/Utils.h | 13 |
9 files changed, 376 insertions, 147 deletions
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index fd9fbae..eea9ee1 100644 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -5,6 +5,7 @@ #include "GraphicsJNI.h"
#include "SkDither.h"
#include "SkUnPreMultiply.h"
+#include "SkStream.h"
#include <binder/Parcel.h>
#include "android_os_Parcel.h"
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index c433874..16beb02 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -202,7 +202,7 @@ private: // since we "may" create a purgeable imageref, we require the stream be ref'able // i.e. dynamically allocated, since its lifetime may exceed the current stack // frame. -static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, +static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options, bool allowPurgeable, bool forcePurgeable = false) { int sampleSize = 1; @@ -459,26 +459,17 @@ static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteA jobject padding, jobject options) { jobject bitmap = NULL; - SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 0); + SkAutoTUnref<SkStreamRewindable> stream(GetRewindableStream(env, is, storage)); - if (stream) { + if (stream.get()) { // for now we don't allow purgeable with java inputstreams + // FIXME: GetRewindableStream may have made a copy, in which case + // purgeable should be allowed. bitmap = doDecode(env, stream, padding, options, false, false); - stream->unref(); } return bitmap; } -static ssize_t getFDSize(int fd) { - off64_t curr = ::lseek64(fd, 0, SEEK_CUR); - if (curr < 0) { - return 0; - } - size_t size = ::lseek(fd, 0, SEEK_END); - ::lseek64(fd, curr, SEEK_SET); - return size; -} - static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fileDescriptor, jobject padding, jobject bitmapFactoryOptions) { @@ -512,44 +503,16 @@ static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fi return doDecode(env, stream, padding, bitmapFactoryOptions, weOwnTheFD); } -/* make a deep copy of the asset, and return it as a stream, or NULL if there - was an error. - */ -static SkStream* copyAssetToStream(Asset* asset) { - // if we could "ref/reopen" the asset, we may not need to copy it here - off64_t size = asset->seek(0, SEEK_SET); - if ((off64_t)-1 == size) { - SkDebugf("---- copyAsset: asset rewind failed\n"); - return NULL; - } - - size = asset->getLength(); - if (size <= 0) { - SkDebugf("---- copyAsset: asset->getLength() returned %d\n", size); - return NULL; - } - - SkStream* stream = new SkMemoryStream(size); - void* data = const_cast<void*>(stream->getMemoryBase()); - off64_t len = asset->read(data, size); - if (len != size) { - SkDebugf("---- copyAsset: asset->read(%d) returned %d\n", size, len); - delete stream; - stream = NULL; - } - return stream; -} - static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jint native_asset, jobject padding, jobject options) { - SkStream* stream; + SkStreamRewindable* stream; Asset* asset = reinterpret_cast<Asset*>(native_asset); bool forcePurgeable = optionsPurgeable(env, options); if (forcePurgeable) { // if we could "ref/reopen" the asset, we may not need to copy it here // and we could assume optionsShareable, since assets are always RO - stream = copyAssetToStream(asset); + stream = CopyAssetToStream(asset); if (stream == NULL) { return NULL; } @@ -559,7 +522,7 @@ static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jint native_asset, stream = new AssetStreamAdaptor(asset); } SkAutoUnref aur(stream); - return doDecode(env, stream, padding, options, true, forcePurgeable); + return doDecode(env, stream, padding, options, forcePurgeable, forcePurgeable); } static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray, @@ -572,7 +535,7 @@ static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray, */ bool purgeable = optionsPurgeable(env, options) && !optionsJustBounds(env, options); AutoJavaByteArray ar(env, byteArray); - SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length, purgeable); + SkMemoryStream* stream = new SkMemoryStream(ar.ptr() + offset, length, purgeable); SkAutoUnref aur(stream); return doDecode(env, stream, NULL, options, purgeable); } diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp index 0c0ebbb..dcab7b3 100644 --- a/core/jni/android/graphics/BitmapRegionDecoder.cpp +++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp @@ -76,27 +76,6 @@ private: int fHeight; }; -static SkMemoryStream* buildSkMemoryStream(SkStream *stream) { - size_t bufferSize = 4096; - size_t streamLen = 0; - size_t len; - char* data = (char*)sk_malloc_throw(bufferSize); - - while ((len = stream->read(data + streamLen, - bufferSize - streamLen)) != 0) { - streamLen += len; - if (streamLen == bufferSize) { - bufferSize *= 2; - data = (char*)sk_realloc_throw(data, bufferSize); - } - } - data = (char*)sk_realloc_throw(data, streamLen); - - SkMemoryStream* streamMem = new SkMemoryStream(); - streamMem->setMemoryOwned(data, streamLen); - return streamMem; -} - static jobject createBitmapRegionDecoder(JNIEnv* env, SkStream* stream) { SkImageDecoder* decoder = SkImageDecoder::Factory(stream); int width, height; @@ -161,14 +140,12 @@ static jobject nativeNewInstanceFromStream(JNIEnv* env, jobject clazz, jbyteArray storage, // byte[] jboolean isShareable) { jobject brd = NULL; - SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 1024); + // for now we don't allow shareable with java inputstreams + SkStream* stream = CopyJavaInputStream(env, is, storage); if (stream) { - // for now we don't allow shareable with java inputstreams - SkMemoryStream* mStream = buildSkMemoryStream(stream); - brd = createBitmapRegionDecoder(env, mStream); - SkSafeUnref(mStream); // the decoder now holds a reference - stream->unref(); + brd = createBitmapRegionDecoder(env, stream); + stream->unref(); // the decoder now holds a reference } return brd; } @@ -176,14 +153,14 @@ static jobject nativeNewInstanceFromStream(JNIEnv* env, jobject clazz, static jobject nativeNewInstanceFromAsset(JNIEnv* env, jobject clazz, jint native_asset, // Asset jboolean isShareable) { - SkStream* stream, *assStream; Asset* asset = reinterpret_cast<Asset*>(native_asset); - assStream = new AssetStreamAdaptor(asset); - stream = buildSkMemoryStream(assStream); - assStream->unref(); + SkAutoTUnref<SkMemoryStream> stream(CopyAssetToStream(asset)); + if (NULL == stream.get()) { + return NULL; + } - jobject brd = createBitmapRegionDecoder(env, stream); - SkSafeUnref(stream); // the decoder now holds a reference + jobject brd = createBitmapRegionDecoder(env, stream.get()); + // The decoder now holds a reference to stream. return brd; } diff --git a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp index aa4cbde..797d155 100644 --- a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp +++ b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp @@ -1,28 +1,83 @@ #include "CreateJavaOutputStreamAdaptor.h" +#include "JNIHelp.h" +#include "SkData.h" +#include "SkRefCnt.h" +#include "SkStream.h" +#include "SkTypes.h" +#include "Utils.h" +#include <androidfw/Asset.h> #define RETURN_NULL_IF_NULL(value) \ do { if (!(value)) { SkASSERT(0); return NULL; } } while (false) +#define RETURN_ZERO_IF_NULL(value) \ + do { if (!(value)) { SkASSERT(0); return 0; } } while (false) + static jmethodID gInputStream_resetMethodID; static jmethodID gInputStream_markMethodID; -static jmethodID gInputStream_availableMethodID; +static jmethodID gInputStream_markSupportedMethodID; static jmethodID gInputStream_readMethodID; static jmethodID gInputStream_skipMethodID; +class RewindableJavaStream; + +/** + * Non-rewindable wrapper for a Java InputStream. + */ class JavaInputStreamAdaptor : public SkStream { public: JavaInputStreamAdaptor(JNIEnv* env, jobject js, jbyteArray ar) : fEnv(env), fJavaInputStream(js), fJavaByteArray(ar) { SkASSERT(ar); - fCapacity = env->GetArrayLength(ar); + fCapacity = env->GetArrayLength(ar); SkASSERT(fCapacity > 0); - fBytesRead = 0; + fBytesRead = 0; + fIsAtEnd = false; + } + + virtual size_t read(void* buffer, size_t size) { + JNIEnv* env = fEnv; + if (NULL == buffer) { + if (0 == size) { + return 0; + } else { + /* InputStream.skip(n) can return <=0 but still not be at EOF + If we see that value, we need to call read(), which will + block if waiting for more data, or return -1 at EOF + */ + size_t amountSkipped = 0; + do { + size_t amount = this->doSkip(size - amountSkipped); + if (0 == amount) { + char tmp; + amount = this->doRead(&tmp, 1); + if (0 == amount) { + // if read returned 0, we're at EOF + fIsAtEnd = true; + break; + } + } + amountSkipped += amount; + } while (amountSkipped < size); + return amountSkipped; + } + } + return this->doRead(buffer, size); + } + + virtual bool isAtEnd() const { + return fIsAtEnd; } - virtual bool rewind() { +private: + // Does not override rewind, since a JavaInputStreamAdaptor's interface + // does not support rewinding. RewindableJavaStream, which is a friend, + // will be able to call this method to rewind. + bool doRewind() { JNIEnv* env = fEnv; fBytesRead = 0; + fIsAtEnd = false; env->CallVoidMethod(fJavaInputStream, gInputStream_resetMethodID); if (env->ExceptionCheck()) { @@ -53,6 +108,7 @@ public: } if (n < 0) { // n == 0 should not be possible, see InputStream read() specifications. + fIsAtEnd = true; break; // eof } @@ -92,58 +148,19 @@ public: return (size_t)skipped; } - size_t doSize() { - JNIEnv* env = fEnv; - jint avail = env->CallIntMethod(fJavaInputStream, - gInputStream_availableMethodID); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); - SkDebugf("------- available threw an exception\n"); - avail = 0; - } - return avail; - } - - virtual size_t read(void* buffer, size_t size) { - JNIEnv* env = fEnv; - if (NULL == buffer) { - if (0 == size) { - return this->doSize(); - } else { - /* InputStream.skip(n) can return <=0 but still not be at EOF - If we see that value, we need to call read(), which will - block if waiting for more data, or return -1 at EOF - */ - size_t amountSkipped = 0; - do { - size_t amount = this->doSkip(size - amountSkipped); - if (0 == amount) { - char tmp; - amount = this->doRead(&tmp, 1); - if (0 == amount) { - // if read returned 0, we're at EOF - break; - } - } - amountSkipped += amount; - } while (amountSkipped < size); - return amountSkipped; - } - } - return this->doRead(buffer, size); - } - -private: JNIEnv* fEnv; jobject fJavaInputStream; // the caller owns this object jbyteArray fJavaByteArray; // the caller owns this object size_t fCapacity; size_t fBytesRead; + bool fIsAtEnd; + + // Allows access to doRewind and fBytesRead. + friend class RewindableJavaStream; }; -SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, - jbyteArray storage, int markSize) { +SkStream* WrapJavaInputStream(JNIEnv* env, jobject stream, + jbyteArray storage) { static bool gInited; if (!gInited) { @@ -154,8 +171,8 @@ SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, "reset", "()V"); gInputStream_markMethodID = env->GetMethodID(inputStream_Clazz, "mark", "(I)V"); - gInputStream_availableMethodID = env->GetMethodID(inputStream_Clazz, - "available", "()I"); + gInputStream_markSupportedMethodID = env->GetMethodID(inputStream_Clazz, + "markSupported", "()Z"); gInputStream_readMethodID = env->GetMethodID(inputStream_Clazz, "read", "([BII)I"); gInputStream_skipMethodID = env->GetMethodID(inputStream_Clazz, @@ -163,18 +180,167 @@ SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, RETURN_NULL_IF_NULL(gInputStream_resetMethodID); RETURN_NULL_IF_NULL(gInputStream_markMethodID); - RETURN_NULL_IF_NULL(gInputStream_availableMethodID); + RETURN_NULL_IF_NULL(gInputStream_markSupportedMethodID); RETURN_NULL_IF_NULL(gInputStream_readMethodID); RETURN_NULL_IF_NULL(gInputStream_skipMethodID); gInited = true; } - if (markSize) { - env->CallVoidMethod(stream, gInputStream_markMethodID, markSize); + return new JavaInputStreamAdaptor(env, stream, storage); +} + +static SkMemoryStream* adaptor_to_mem_stream(SkStream* adaptor) { + SkASSERT(adaptor != NULL); + SkDynamicMemoryWStream wStream; + const int bufferSize = 256 * 1024; // 256 KB, same as ViewStateSerializer. + uint8_t buffer[bufferSize]; + do { + size_t bytesRead = adaptor->read(buffer, bufferSize); + wStream.write(buffer, bytesRead); + } while (!adaptor->isAtEnd()); + SkAutoTUnref<SkData> data(wStream.copyToData()); + return new SkMemoryStream(data.get()); +} + +SkMemoryStream* CopyJavaInputStream(JNIEnv* env, jobject stream, + jbyteArray storage) { + SkAutoTUnref<SkStream> adaptor(WrapJavaInputStream(env, stream, storage)); + if (NULL == adaptor.get()) { + return NULL; } + return adaptor_to_mem_stream(adaptor.get()); +} - return new JavaInputStreamAdaptor(env, stream, storage); +/** + * Wrapper for a Java InputStream which is rewindable and + * has a length. + */ +class RewindableJavaStream : public SkStreamRewindable { +public: + // RewindableJavaStream takes ownership of adaptor. + RewindableJavaStream(JavaInputStreamAdaptor* adaptor, size_t length) + : fAdaptor(adaptor) + , fLength(length) { + SkASSERT(fAdaptor != NULL); + } + + virtual ~RewindableJavaStream() { + fAdaptor->unref(); + } + + virtual bool rewind() { + return fAdaptor->doRewind(); + } + + virtual size_t read(void* buffer, size_t size) { + return fAdaptor->read(buffer, size); + } + + virtual bool isAtEnd() const { + return fAdaptor->isAtEnd(); + } + + virtual size_t getLength() const { + return fLength; + } + + virtual bool hasLength() const { + return true; + } + + virtual SkStreamRewindable* duplicate() const { + // Duplicating this stream requires rewinding and + // reading, which modify this Stream (and could + // fail, leaving this one invalid). + SkASSERT(false); + return NULL; + } + +private: + JavaInputStreamAdaptor* fAdaptor; + const size_t fLength; +}; + +/** + * If jstream is a ByteArrayInputStream, return its remaining length. Otherwise + * return 0. + */ +static size_t get_length_from_byte_array_stream(JNIEnv* env, jobject jstream) { + static jclass byteArrayInputStream_Clazz; + static jfieldID countField; + static jfieldID posField; + + byteArrayInputStream_Clazz = env->FindClass("java/io/ByteArrayInputStream"); + RETURN_ZERO_IF_NULL(byteArrayInputStream_Clazz); + + countField = env->GetFieldID(byteArrayInputStream_Clazz, "count", "I"); + RETURN_ZERO_IF_NULL(byteArrayInputStream_Clazz); + posField = env->GetFieldID(byteArrayInputStream_Clazz, "pos", "I"); + RETURN_ZERO_IF_NULL(byteArrayInputStream_Clazz); + + if (env->IsInstanceOf(jstream, byteArrayInputStream_Clazz)) { + // Return the remaining length, to keep the same behavior of using the rest of the + // stream. + return env->GetIntField(jstream, countField) - env->GetIntField(jstream, posField); + } + return 0; +} + +/** + * If jstream is a class that has a length, return it. Otherwise + * return 0. + * Only checks for a set of subclasses. + */ +static size_t get_length_if_supported(JNIEnv* env, jobject jstream) { + size_t len = get_length_from_byte_array_stream(env, jstream); + if (len > 0) { + return len; + } + return 0; +} + +SkStreamRewindable* GetRewindableStream(JNIEnv* env, jobject stream, + jbyteArray storage) { + SkAutoTUnref<SkStream> adaptor(WrapJavaInputStream(env, stream, storage)); + if (NULL == adaptor.get()) { + return NULL; + } + + const size_t length = get_length_if_supported(env, stream); + if (length > 0 && env->CallBooleanMethod(stream, gInputStream_markSupportedMethodID)) { + // Set the readLimit for mark to the end of the stream, so it can + // be rewound regardless of how much has been read. + env->CallVoidMethod(stream, gInputStream_markMethodID, length); + // RewindableJavaStream will unref adaptor when it is destroyed. + return new RewindableJavaStream(static_cast<JavaInputStreamAdaptor*>(adaptor.detach()), + length); + } + + return adaptor_to_mem_stream(adaptor.get()); +} + +android::AssetStreamAdaptor* CheckForAssetStream(JNIEnv* env, jobject jstream) { + static jclass assetInputStream_Clazz; + static jmethodID getAssetIntMethodID; + + assetInputStream_Clazz = env->FindClass("android/content/res/AssetManager$AssetInputStream"); + RETURN_NULL_IF_NULL(assetInputStream_Clazz); + + getAssetIntMethodID = env->GetMethodID(assetInputStream_Clazz, "getAssetInt", "()I"); + RETURN_NULL_IF_NULL(getAssetIntMethodID); + + if (!env->IsInstanceOf(jstream, assetInputStream_Clazz)) { + return NULL; + } + + jint jasset = env->CallIntMethod(jstream, getAssetIntMethodID); + android::Asset* a = reinterpret_cast<android::Asset*>(jasset); + if (NULL == a) { + jniThrowNullPointerException(env, "NULL native asset"); + return NULL; + } + return new android::AssetStreamAdaptor(a); } /////////////////////////////////////////////////////////////////////////////// diff --git a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h index c34c96a..5218dc5 100644 --- a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h +++ b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h @@ -3,10 +3,70 @@ //#include <android_runtime/AndroidRuntime.h> #include "jni.h" -#include "SkStream.h" -SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, - jbyteArray storage, int markSize = 0); +namespace android { + class AssetStreamAdaptor; +} + +class SkMemoryStream; +class SkStream; +class SkStreamRewindable; +class SkWStream; + +/** + * Return an adaptor from a Java InputStream to an SkStream. + * @param env JNIEnv object. + * @param stream Pointer to Java InputStream. + * @param storage Java byte array for retrieving data from the + * Java InputStream. + * @return SkStream Simple subclass of SkStream which supports its + * basic methods like reading. Only valid until the calling + * function returns, since the Java InputStream is not managed + * by the SkStream. + */ +SkStream* WrapJavaInputStream(JNIEnv* env, jobject stream, + jbyteArray storage); + +/** + * Copy a Java InputStream. + * @param env JNIEnv object. + * @param stream Pointer to Java InputStream. + * @param storage Java byte array for retrieving data from the + * Java InputStream. + * @return SkMemoryStream The data in stream will be copied to a new + * SkMemoryStream. + * FIXME: Could return a more generic return type if ViewStateSerializer + * did not require an SkMemoryStream. + */ +SkMemoryStream* CopyJavaInputStream(JNIEnv* env, jobject stream, + jbyteArray storage); + +/** + * Get a rewindable stream from a Java InputStream. + * @param env JNIEnv object. + * @param stream Pointer to Java InputStream. + * @param storage Java byte array for retrieving data from the + * Java InputStream. + * @return SkStreamRewindable Either a wrapper around the Java + * InputStream, if possible, or a copy which is rewindable. + * Since it may be a wrapper, must not be used after the + * caller returns, like the result of WrapJavaInputStream. + */ +SkStreamRewindable* GetRewindableStream(JNIEnv* env, jobject stream, + jbyteArray storage); + +/** + * If the Java InputStream is an AssetInputStream, return an adaptor. + * This should not be used after the calling function returns, since + * the caller may close the asset. Returns NULL if the stream is + * not an AssetInputStream. + * @param env JNIEnv object. + * @param stream Pointer to Java InputStream. + * @return AssetStreamAdaptor representing the InputStream, or NULL. + * Must not be held onto. + */ +android::AssetStreamAdaptor* CheckForAssetStream(JNIEnv* env, jobject stream); + SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream, jbyteArray storage); diff --git a/core/jni/android/graphics/Movie.cpp b/core/jni/android/graphics/Movie.cpp index 4f64ff8..2eae841 100644 --- a/core/jni/android/graphics/Movie.cpp +++ b/core/jni/android/graphics/Movie.cpp @@ -1,8 +1,10 @@ +#include "ScopedLocalRef.h" #include "SkMovie.h" #include "SkStream.h" #include "GraphicsJNI.h" #include "SkTemplates.h" #include "SkUtils.h" +#include "Utils.h" #include "CreateJavaOutputStreamAdaptor.h" #include <androidfw/Asset.h> @@ -83,9 +85,14 @@ static jobject movie_decodeStream(JNIEnv* env, jobject clazz, jobject istream) { NPE_CHECK_RETURN_ZERO(env, istream); - // what is the lifetime of the array? Can the skstream hold onto it? - jbyteArray byteArray = env->NewByteArray(16*1024); - SkStream* strm = CreateJavaInputStreamAdaptor(env, istream, byteArray); + SkStreamRewindable* strm = CheckForAssetStream(env, istream); + jbyteArray byteArray = NULL; + ScopedLocalRef<jbyteArray> scoper(env, NULL); + if (NULL == strm) { + byteArray = env->NewByteArray(16*1024); + scoper.reset(byteArray); + strm = GetRewindableStream(env, istream, byteArray); + } if (NULL == strm) { return 0; } diff --git a/core/jni/android/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp index 9c02219..dff2b18 100644 --- a/core/jni/android/graphics/Picture.cpp +++ b/core/jni/android/graphics/Picture.cpp @@ -20,6 +20,7 @@ #include "SkCanvas.h" #include "SkPicture.h" +#include "SkStream.h" #include "SkTemplates.h" #include "CreateJavaOutputStreamAdaptor.h" @@ -38,10 +39,9 @@ public: static SkPicture* deserialize(JNIEnv* env, jobject, jobject jstream, jbyteArray jstorage) { SkPicture* picture = NULL; - SkStream* strm = CreateJavaInputStreamAdaptor(env, jstream, jstorage); - if (strm) { - picture = SkPicture::CreateFromStream(strm); - delete strm; + SkAutoTUnref<SkStream> strm(WrapJavaInputStream(env, jstream, jstorage)); + if (strm.get()) { + picture = SkPicture::CreateFromStream(strm.get()); } return picture; } diff --git a/core/jni/android/graphics/Utils.cpp b/core/jni/android/graphics/Utils.cpp index cf6977e..b7d1f3a 100644 --- a/core/jni/android/graphics/Utils.cpp +++ b/core/jni/android/graphics/Utils.cpp @@ -28,12 +28,28 @@ bool AssetStreamAdaptor::rewind() { return true; } +size_t AssetStreamAdaptor::getLength() const { + return fAsset->getLength(); +} + +bool AssetStreamAdaptor::isAtEnd() const { + return fAsset->getRemainingLength() == 0; +} + +SkStreamRewindable* AssetStreamAdaptor::duplicate() const { + SkASSERT(false); + // Cannot create a duplicate, since each AssetStreamAdaptor + // would be modifying the Asset. + //return new AssetStreamAdaptor(fAsset); + return NULL; +} + size_t AssetStreamAdaptor::read(void* buffer, size_t size) { ssize_t amount; if (NULL == buffer) { - if (0 == size) { // caller is asking us for our total length - return fAsset->getLength(); + if (0 == size) { + return 0; } // asset->seek returns new total offset // we want to return amount that was skipped @@ -62,6 +78,34 @@ size_t AssetStreamAdaptor::read(void* buffer, size_t size) { return amount; } +SkMemoryStream* android::CopyAssetToStream(Asset* asset) { + if (NULL == asset) { + return NULL; + } + + off64_t size = asset->seek(0, SEEK_SET); + if ((off64_t)-1 == size) { + SkDebugf("---- copyAsset: asset rewind failed\n"); + return NULL; + } + + size = asset->getLength(); + if (size <= 0) { + SkDebugf("---- copyAsset: asset->getLength() returned %d\n", size); + return NULL; + } + + SkMemoryStream* stream = new SkMemoryStream(size); + void* data = const_cast<void*>(stream->getMemoryBase()); + off64_t len = asset->read(data, size); + if (len != size) { + SkDebugf("---- copyAsset: asset->read(%d) returned %d\n", size, len); + delete stream; + stream = NULL; + } + return stream; +} + jobject android::nullObjectReturn(const char msg[]) { if (msg) { SkDebugf("--- %s\n", msg); diff --git a/core/jni/android/graphics/Utils.h b/core/jni/android/graphics/Utils.h index 75ceaa2..a1ac72a 100644 --- a/core/jni/android/graphics/Utils.h +++ b/core/jni/android/graphics/Utils.h @@ -26,16 +26,27 @@ namespace android { -class AssetStreamAdaptor : public SkStream { +class AssetStreamAdaptor : public SkStreamRewindable { public: AssetStreamAdaptor(Asset* a) : fAsset(a) {} virtual bool rewind(); virtual size_t read(void* buffer, size_t size); + virtual bool hasLength() const { return true; } + virtual size_t getLength() const; + virtual bool isAtEnd() const; + virtual SkStreamRewindable* duplicate() const; private: Asset* fAsset; }; +/** + * Make a deep copy of the asset, and return it as a stream, or NULL if there + * was an error. + * FIXME: If we could "ref/reopen" the asset, we may not need to copy it here. + */ + +SkMemoryStream* CopyAssetToStream(Asset*); /** Restore the file descriptor's offset in our destructor */ |