static jclass gImageDecoder_class;
static jclass gSize_class;
-static jclass gIncomplete_class;
+static jclass gDecodeException_class;
static jclass gCanvas_class;
static jmethodID gImageDecoder_constructorMethodID;
static jmethodID gImageDecoder_postProcessMethodID;
static jmethodID gSize_constructorMethodID;
-static jmethodID gIncomplete_constructorMethodID;
+static jmethodID gDecodeException_constructorMethodID;
static jmethodID gCallback_onPartialImageMethodID;
static jmethodID gCanvas_constructorMethodID;
static jmethodID gCanvas_releaseMethodID;
-static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream) {
+// Clear and return any pending exception for handling other than throwing directly.
+static jthrowable get_and_clear_exception(JNIEnv* env) {
+ jthrowable jexception = env->ExceptionOccurred();
+ if (jexception) {
+ env->ExceptionClear();
+ }
+ return jexception;
+}
+
+// Throw a new ImageDecoder.DecodeException. Returns null for convenience.
+static jobject throw_exception(JNIEnv* env, ImageDecoder::Error error, const char* msg,
+ jthrowable cause, jobject source) {
+ jstring jstr = nullptr;
+ if (msg) {
+ jstr = env->NewStringUTF(msg);
+ if (!jstr) {
+ // Out of memory.
+ return nullptr;
+ }
+ }
+ jthrowable exception = (jthrowable) env->NewObject(gDecodeException_class,
+ gDecodeException_constructorMethodID, error, jstr, cause, source);
+ // Only throw if not out of memory.
+ if (exception) {
+ env->Throw(exception);
+ }
+ return nullptr;
+}
+
+static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream, jobject source) {
if (!stream.get()) {
- doThrowIOE(env, "Failed to create a stream");
- return nullptr;
+ return throw_exception(env, ImageDecoder::kSourceError, "Failed to create a stream",
+ nullptr, source);
}
std::unique_ptr<ImageDecoder> decoder(new ImageDecoder);
SkCodec::Result result;
auto codec = SkCodec::MakeFromStream(std::move(stream), &result, decoder->mPeeker.get());
+ if (jthrowable jexception = get_and_clear_exception(env)) {
+ return throw_exception(env, ImageDecoder::kSourceException, "", jexception, source);
+ }
if (!codec) {
switch (result) {
case SkCodec::kIncompleteInput:
- env->ThrowNew(gIncomplete_class, "Incomplete input");
- break;
+ return throw_exception(env, ImageDecoder::kSourceIncomplete, "", nullptr, source);
default:
SkString msg;
msg.printf("Failed to create image decoder with message '%s'",
SkCodec::ResultToString(result));
- doThrowIOE(env, msg.c_str());
- break;
+ return throw_exception(env, ImageDecoder::kSourceError, msg.c_str(), nullptr,
+ source);
}
- return nullptr;
}
- // FIXME: Avoid parsing the whole image?
const bool animated = codec->getFrameCount() > 1;
+ if (jthrowable jexception = get_and_clear_exception(env)) {
+ return throw_exception(env, ImageDecoder::kSourceException, "", jexception, source);
+ }
+
decoder->mCodec = SkAndroidCodec::MakeFromCodec(std::move(codec),
SkAndroidCodec::ExifOrientationBehavior::kRespect);
if (!decoder->mCodec.get()) {
- doThrowIOE(env, "Could not create AndroidCodec");
- return nullptr;
+ return throw_exception(env, ImageDecoder::kSourceError, "", nullptr, source);
}
+
const auto& info = decoder->mCodec->getInfo();
const int width = info.width();
const int height = info.height();
}
static jobject ImageDecoder_nCreateFd(JNIEnv* env, jobject /*clazz*/,
- jobject fileDescriptor) {
+ jobject fileDescriptor, jobject source) {
int descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor);
struct stat fdStat;
if (fstat(descriptor, &fdStat) == -1) {
- doThrowIOE(env, "broken file descriptor; fstat returned -1");
- return nullptr;
+ return throw_exception(env, ImageDecoder::kSourceError,
+ "broken file descriptor; fstat returned -1", nullptr, source);
}
int dupDescriptor = dup(descriptor);
FILE* file = fdopen(dupDescriptor, "r");
if (file == NULL) {
close(dupDescriptor);
- doThrowIOE(env, "Could not open file");
- return nullptr;
+ return throw_exception(env, ImageDecoder::kSourceError, "Could not open file", nullptr,
+ source);
}
std::unique_ptr<SkFILEStream> fileStream(new SkFILEStream(file));
if (::lseek(descriptor, 0, SEEK_CUR) == 0) {
- return native_create(env, std::move(fileStream));
+ return native_create(env, std::move(fileStream), source);
}
// FIXME: This allows us to pretend the current location is the beginning,
// point as the beginning.
std::unique_ptr<SkStream> stream(SkFrontBufferedStream::Make(std::move(fileStream),
SkCodec::MinBufferedBytesNeeded()));
- return native_create(env, std::move(stream));
+ return native_create(env, std::move(stream), source);
}
static jobject ImageDecoder_nCreateInputStream(JNIEnv* env, jobject /*clazz*/,
- jobject is, jbyteArray storage) {
+ jobject is, jbyteArray storage, jobject source) {
std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage, false));
if (!stream.get()) {
- doThrowIOE(env, "Failed to create stream!");
- return nullptr;
+ return throw_exception(env, ImageDecoder::kSourceError, "Failed to create a stream",
+ nullptr, source);
}
+
std::unique_ptr<SkStream> bufferedStream(
SkFrontBufferedStream::Make(std::move(stream),
SkCodec::MinBufferedBytesNeeded()));
- return native_create(env, std::move(bufferedStream));
+ return native_create(env, std::move(bufferedStream), source);
}
-static jobject ImageDecoder_nCreateAsset(JNIEnv* env, jobject /*clazz*/, jlong assetPtr) {
+static jobject ImageDecoder_nCreateAsset(JNIEnv* env, jobject /*clazz*/, jlong assetPtr,
+ jobject source) {
Asset* asset = reinterpret_cast<Asset*>(assetPtr);
std::unique_ptr<SkStream> stream(new AssetStreamAdaptor(asset));
- return native_create(env, std::move(stream));
+ return native_create(env, std::move(stream), source);
}
static jobject ImageDecoder_nCreateByteBuffer(JNIEnv* env, jobject /*clazz*/, jobject jbyteBuffer,
- jint initialPosition, jint limit) {
+ jint initialPosition, jint limit, jobject source) {
std::unique_ptr<SkStream> stream = CreateByteBufferStreamAdaptor(env, jbyteBuffer,
initialPosition, limit);
if (!stream) {
- doThrowIOE(env, "Failed to read ByteBuffer");
- return nullptr;
+ return throw_exception(env, ImageDecoder::kSourceError, "Failed to read ByteBuffer",
+ nullptr, source);
}
- return native_create(env, std::move(stream));
+ return native_create(env, std::move(stream), source);
}
static jobject ImageDecoder_nCreateByteArray(JNIEnv* env, jobject /*clazz*/, jbyteArray byteArray,
- jint offset, jint length) {
+ jint offset, jint length, jobject source) {
std::unique_ptr<SkStream> stream(CreateByteArrayStreamAdaptor(env, byteArray, offset, length));
- return native_create(env, std::move(stream));
+ return native_create(env, std::move(stream), source);
}
jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, std::unique_ptr<Canvas> canvas) {
return env->CallIntMethod(jimageDecoder, gImageDecoder_postProcessMethodID, jcanvas);
}
-// Note: jpostProcess points to an ImageDecoder object if it has a PostProcess object, and nullptr
-// otherwise.
static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
- jobject jcallback, jobject jpostProcess,
+ jobject jdecoder, jboolean jpostProcess,
jint desiredWidth, jint desiredHeight, jobject jsubset,
jboolean requireMutable, jint allocator,
jboolean requireUnpremul, jboolean preferRamOverQuality,
SkAndroidCodec::AndroidOptions options;
options.fSampleSize = sampleSize;
auto result = codec->getAndroidPixels(decodeInfo, bm.getPixels(), bm.rowBytes(), &options);
- jthrowable jexception = env->ExceptionOccurred();
- if (jexception) {
- env->ExceptionClear();
- }
- int onPartialImageError = jexception ? 1 // ImageDecoder.java's ERROR_SOURCE_EXCEPTION
+ jthrowable jexception = get_and_clear_exception(env);
+ int onPartialImageError = jexception ? ImageDecoder::kSourceException
: 0; // No error.
switch (result) {
case SkCodec::kSuccess:
break;
case SkCodec::kIncompleteInput:
if (!jexception) {
- onPartialImageError = 2; // ImageDecoder.java's ERROR_SOURCE_EXCEPTION
+ onPartialImageError = ImageDecoder::kSourceIncomplete;
}
break;
case SkCodec::kErrorInInput:
if (!jexception) {
- onPartialImageError = 3; // ImageDecoder.java's ERROR_SOURCE_ERROR
+ onPartialImageError = ImageDecoder::kSourceError;
}
break;
default:
return nullptr;
}
- if (jexception || onPartialImageError) {
- bool throwException = !jcallback ||
- !env->CallBooleanMethod(jcallback, gCallback_onPartialImageMethodID,
- onPartialImageError);
+ if (onPartialImageError) {
+ env->CallVoidMethod(jdecoder, gCallback_onPartialImageMethodID, onPartialImageError,
+ jexception);
if (env->ExceptionCheck()) {
return nullptr;
}
-
- if (throwException) {
- if (jexception) {
- env->Throw(jexception);
- } else if (onPartialImageError == 2) {
- env->ThrowNew(gIncomplete_class, "Incomplete input");
- } else {
- doThrowIOE(env, "image has an error!");
- }
- return nullptr;
- }
}
float scaleX = 1.0f;
SkIRect subset;
GraphicsJNI::jrect_to_irect(env, jsubset, &subset);
- // FIXME: If there is no scale, should this instead call
- // SkBitmap::extractSubset? If we could upload a subset
- // (b/70626068), this would save memory and time. Even for a
- // software Bitmap, the extra speed might be worth the memory
- // tradeoff if the subset is large?
translateX = -subset.fLeft;
translateY = -subset.fTop;
desiredWidth = subset.width();
if (jpostProcess) {
std::unique_ptr<Canvas> canvas(Canvas::create_canvas(bm));
- jint pixelFormat = postProcessAndRelease(env, jpostProcess, std::move(canvas));
+ jint pixelFormat = postProcessAndRelease(env, jdecoder, std::move(canvas));
if (env->ExceptionCheck()) {
return nullptr;
}
}
static const JNINativeMethod gImageDecoderMethods[] = {
- { "nCreate", "(J)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateAsset },
- { "nCreate", "(Ljava/nio/ByteBuffer;II)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteBuffer },
- { "nCreate", "([BII)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteArray },
- { "nCreate", "(Ljava/io/InputStream;[B)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateInputStream },
- { "nCreate", "(Ljava/io/FileDescriptor;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateFd },
- { "nDecodeBitmap", "(JLandroid/graphics/ImageDecoder;Landroid/graphics/ImageDecoder;IILandroid/graphics/Rect;ZIZZZ)Landroid/graphics/Bitmap;",
+ { "nCreate", "(JLandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateAsset },
+ { "nCreate", "(Ljava/nio/ByteBuffer;IILandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteBuffer },
+ { "nCreate", "([BIILandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteArray },
+ { "nCreate", "(Ljava/io/InputStream;[BLandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateInputStream },
+ { "nCreate", "(Ljava/io/FileDescriptor;Landroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateFd },
+ { "nDecodeBitmap", "(JLandroid/graphics/ImageDecoder;ZIILandroid/graphics/Rect;ZIZZZ)Landroid/graphics/Bitmap;",
(void*) ImageDecoder_nDecodeBitmap },
{ "nGetSampledSize","(JI)Landroid/util/Size;", (void*) ImageDecoder_nGetSampledSize },
{ "nGetPadding", "(JLandroid/graphics/Rect;)V", (void*) ImageDecoder_nGetPadding },
gSize_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/util/Size"));
gSize_constructorMethodID = GetMethodIDOrDie(env, gSize_class, "<init>", "(II)V");
- gIncomplete_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder$IncompleteException"));
- gIncomplete_constructorMethodID = GetMethodIDOrDie(env, gIncomplete_class, "<init>", "()V");
+ gDecodeException_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder$DecodeException"));
+ gDecodeException_constructorMethodID = GetMethodIDOrDie(env, gDecodeException_class, "<init>", "(ILjava/lang/String;Ljava/lang/Throwable;Landroid/graphics/ImageDecoder$Source;)V");
- gCallback_onPartialImageMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "onPartialImage", "(I)Z");
+ gCallback_onPartialImageMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "onPartialImage", "(ILjava/lang/Throwable;)V");
gCanvas_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Canvas"));
gCanvas_constructorMethodID = GetMethodIDOrDie(env, gCanvas_class, "<init>", "(J)V");
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.content.ContentResolver;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
@Override
public ImageDecoder createImageDecoder() throws IOException {
- return nCreate(mData, mOffset, mLength);
+ return nCreate(mData, mOffset, mLength, this);
}
}
if (!mBuffer.isDirect() && mBuffer.hasArray()) {
int offset = mBuffer.arrayOffset() + mBuffer.position();
int length = mBuffer.limit() - mBuffer.position();
- return nCreate(mBuffer.array(), offset, length);
+ return nCreate(mBuffer.array(), offset, length, this);
}
- return nCreate(mBuffer, mBuffer.position(), mBuffer.limit());
+ return nCreate(mBuffer, mBuffer.position(), mBuffer.limit(), this);
}
}
throw new FileNotFoundException(mUri.toString());
}
- return createFromStream(is, true);
+ return createFromStream(is, true, this);
}
final FileDescriptor fd = assetFd.getFileDescriptor();
try {
try {
Os.lseek(fd, offset, SEEK_SET);
- decoder = nCreate(fd);
+ decoder = nCreate(fd, this);
} catch (ErrnoException e) {
- decoder = createFromStream(new FileInputStream(fd), true);
+ decoder = createFromStream(new FileInputStream(fd), true, this);
}
} finally {
if (decoder == null) {
}
@NonNull
- private static ImageDecoder createFromFile(@NonNull File file) throws IOException {
+ private static ImageDecoder createFromFile(@NonNull File file,
+ @NonNull Source source) throws IOException {
FileInputStream stream = new FileInputStream(file);
FileDescriptor fd = stream.getFD();
try {
Os.lseek(fd, 0, SEEK_CUR);
} catch (ErrnoException e) {
- return createFromStream(stream, true);
+ return createFromStream(stream, true, source);
}
ImageDecoder decoder = null;
try {
- decoder = nCreate(fd);
+ decoder = nCreate(fd, source);
} finally {
if (decoder == null) {
IoUtils.closeQuietly(stream);
@NonNull
private static ImageDecoder createFromStream(@NonNull InputStream is,
- boolean closeInputStream) throws IOException {
+ boolean closeInputStream, Source source) throws IOException {
// Arbitrary size matches BitmapFactory.
byte[] storage = new byte[16 * 1024];
ImageDecoder decoder = null;
try {
- decoder = nCreate(is, storage);
+ decoder = nCreate(is, storage, source);
} finally {
if (decoder == null) {
if (closeInputStream) {
}
InputStream is = mInputStream;
mInputStream = null;
- return createFromStream(is, false);
+ return createFromStream(is, false, this);
}
}
}
}
AssetInputStream ais = mAssetInputStream;
mAssetInputStream = null;
- return createFromAsset(ais);
+ return createFromAsset(ais, this);
}
}
}
mResDensity = value.density;
}
- return createFromAsset((AssetInputStream) is);
+ return createFromAsset((AssetInputStream) is, this);
}
}
/**
* ImageDecoder will own the AssetInputStream.
*/
- private static ImageDecoder createFromAsset(AssetInputStream ais) throws IOException {
+ private static ImageDecoder createFromAsset(AssetInputStream ais,
+ Source source) throws IOException {
ImageDecoder decoder = null;
try {
long asset = ais.getNativeAsset();
- decoder = nCreate(asset);
+ decoder = nCreate(asset, source);
} finally {
if (decoder == null) {
IoUtils.closeQuietly(ais);
@Override
public ImageDecoder createImageDecoder() throws IOException {
InputStream is = mAssets.open(mFileName);
- return createFromAsset((AssetInputStream) is);
+ return createFromAsset((AssetInputStream) is, this);
}
}
@Override
public ImageDecoder createImageDecoder() throws IOException {
- return createFromFile(mFile);
+ return createFromFile(mFile, this);
}
}
}
};
- /**
- * Thrown if the provided data is incomplete.
+ /** @removed
+ * @deprecated Subsumed by {@link #DecodeException}.
*/
+ @java.lang.Deprecated
public static class IncompleteException extends IOException {};
/**
*/
public static final int ERROR_SOURCE_ERROR = 3;
+ /** @hide **/
@Retention(SOURCE)
- @IntDef({ ERROR_SOURCE_EXCEPTION, ERROR_SOURCE_INCOMPLETE, ERROR_SOURCE_ERROR })
+ @IntDef(value = { ERROR_SOURCE_EXCEPTION, ERROR_SOURCE_INCOMPLETE, ERROR_SOURCE_ERROR },
+ prefix = {"ERROR_"})
public @interface Error {};
/**
+ * Information about an interrupted decode.
+ */
+ public static final class DecodeException extends IOException {
+ @Error final int mError;
+ @NonNull final Source mSource;
+
+ DecodeException(@Error int error, @Nullable Throwable cause, @NonNull Source source) {
+ super(errorMessage(error, cause), cause);
+ mError = error;
+ mSource = source;
+ }
+
+ /**
+ * Private method called by JNI.
+ */
+ @SuppressWarnings("unused")
+ DecodeException(@Error int error, @Nullable String msg, @Nullable Throwable cause,
+ @NonNull Source source) {
+ super(msg + errorMessage(error, cause), cause);
+ mError = error;
+ mSource = source;
+ }
+
+ /**
+ * Retrieve the reason that decoding was interrupted.
+ *
+ * <p>If the error is {@link #ERROR_SOURCE_EXCEPTION}, the underlying
+ * {@link java.lang.Throwable} can be retrieved with
+ * {@link java.lang.Throwable#getCause}.</p>
+ */
+ @Error
+ public int getError() {
+ return mError;
+ }
+
+ /**
+ * Retrieve the {@link Source} that was interrupted.
+ */
+ @NonNull
+ public Source getSource() {
+ return mSource;
+ }
+
+ private static String errorMessage(@Error int error, @Nullable Throwable cause) {
+ switch (error) {
+ case ERROR_SOURCE_EXCEPTION:
+ return "Exception in input: " + cause;
+ case ERROR_SOURCE_INCOMPLETE:
+ return "Input was incomplete.";
+ case ERROR_SOURCE_ERROR:
+ return "Input contained an error.";
+ default:
+ return "";
+ }
+ }
+ }
+
+ /**
* Optional listener supplied to the ImageDecoder.
*
* Without this listener, errors will throw {@link java.io.IOException}.
* optionally finish the rest of the decode/creation process to create
* a partial {@link Drawable}/{@link Bitmap}.
*
- * @param error indicating what interrupted the decode.
- * @param source that had the error.
+ * @param e containing information about the decode interruption.
* @return True to create and return a {@link Drawable}/{@link Bitmap}
* with partial data. False (which is the default) to abort the
- * decode and throw {@link java.io.IOException}.
+ * decode and throw {@code e}.
*/
- public boolean onPartialImage(@Error int error, @NonNull Source source);
+ boolean onPartialImage(@NonNull DecodeException e);
};
// Fields
* Internal API used to generate bitmaps for use by Drawables (i.e. BitmapDrawable)
* @hide
*/
+ @TestApi
public static Source createSource(Resources res, InputStream is, int density) {
return new InputStreamSource(res, is, density);
}
@NonNull
private Bitmap decodeBitmapInternal() throws IOException {
checkState();
- // nDecodeBitmap calls onPartialImage only if mOnPartialImageListener
- // exists
- ImageDecoder partialImagePtr = mOnPartialImageListener == null ? null : this;
- // nDecodeBitmap calls postProcessAndRelease only if mPostProcessor
- // exists.
- ImageDecoder postProcessPtr = mPostProcessor == null ? null : this;
- return nDecodeBitmap(mNativePtr, partialImagePtr,
- postProcessPtr, mDesiredWidth, mDesiredHeight, mCropRect,
+ return nDecodeBitmap(mNativePtr, this, mPostProcessor != null,
+ mDesiredWidth, mDesiredHeight, mCropRect,
mMutable, mAllocator, mRequireUnpremultiplied,
mConserveMemory, mDecodeAsAlphaMask);
}
* Private method called by JNI.
*/
@SuppressWarnings("unused")
- private boolean onPartialImage(@Error int error) {
- return mOnPartialImageListener.onPartialImage(error, mSource);
+ private void onPartialImage(@Error int error, @Nullable Throwable cause)
+ throws DecodeException {
+ DecodeException exception = new DecodeException(error, cause, mSource);
+ if (mOnPartialImageListener == null
+ || !mOnPartialImageListener.onPartialImage(exception)) {
+ throw exception;
+ }
}
- private static native ImageDecoder nCreate(long asset) throws IOException;
- private static native ImageDecoder nCreate(ByteBuffer buffer,
- int position,
- int limit) throws IOException;
- private static native ImageDecoder nCreate(byte[] data, int offset,
- int length) throws IOException;
- private static native ImageDecoder nCreate(InputStream is, byte[] storage);
+ private static native ImageDecoder nCreate(long asset, Source src) throws IOException;
+ private static native ImageDecoder nCreate(ByteBuffer buffer, int position,
+ int limit, Source src) throws IOException;
+ private static native ImageDecoder nCreate(byte[] data, int offset, int length,
+ Source src) throws IOException;
+ private static native ImageDecoder nCreate(InputStream is, byte[] storage,
+ Source src) throws IOException;
// The fd must be seekable.
- private static native ImageDecoder nCreate(FileDescriptor fd) throws IOException;
+ private static native ImageDecoder nCreate(FileDescriptor fd, Source src) throws IOException;
@NonNull
private static native Bitmap nDecodeBitmap(long nativePtr,
- @Nullable ImageDecoder partialImageListener,
- @Nullable ImageDecoder postProcessor,
+ @NonNull ImageDecoder decoder,
+ boolean doPostProcess,
int width, int height,
@Nullable Rect cropRect, boolean mutable,
int allocator, boolean requireUnpremul,