Re-write onPartialImage API
am: 1d2bf2b846
Change-Id: I7436bbe89b9efd9a406958b14f79294e982c5b3a
This commit is contained in:
@@ -13638,7 +13638,9 @@ package android.graphics {
|
||||
field public static final int ERROR_SOURCE_INCOMPLETE = 2; // 0x2
|
||||
}
|
||||
|
||||
public static abstract class ImageDecoder.Error implements java.lang.annotation.Annotation {
|
||||
public static final class ImageDecoder.DecodeException extends java.io.IOException {
|
||||
method public int getError();
|
||||
method public android.graphics.ImageDecoder.Source getSource();
|
||||
}
|
||||
|
||||
public static class ImageDecoder.ImageInfo {
|
||||
@@ -13647,16 +13649,12 @@ package android.graphics {
|
||||
method public boolean isAnimated();
|
||||
}
|
||||
|
||||
public static class ImageDecoder.IncompleteException extends java.io.IOException {
|
||||
ctor public ImageDecoder.IncompleteException();
|
||||
}
|
||||
|
||||
public static abstract interface ImageDecoder.OnHeaderDecodedListener {
|
||||
method public abstract void onHeaderDecoded(android.graphics.ImageDecoder, android.graphics.ImageDecoder.ImageInfo, android.graphics.ImageDecoder.Source);
|
||||
}
|
||||
|
||||
public static abstract interface ImageDecoder.OnPartialImageListener {
|
||||
method public abstract boolean onPartialImage(int, android.graphics.ImageDecoder.Source);
|
||||
method public abstract boolean onPartialImage(android.graphics.ImageDecoder.DecodeException);
|
||||
}
|
||||
|
||||
public static abstract class ImageDecoder.Source {
|
||||
|
||||
@@ -157,6 +157,10 @@ package android.graphics {
|
||||
method public deprecated android.graphics.ImageDecoder setAsAlphaMask(boolean);
|
||||
}
|
||||
|
||||
public static deprecated class ImageDecoder.IncompleteException extends java.io.IOException {
|
||||
ctor public ImageDecoder.IncompleteException();
|
||||
}
|
||||
|
||||
public deprecated class LayerRasterizer extends android.graphics.Rasterizer {
|
||||
ctor public LayerRasterizer();
|
||||
method public void addLayer(android.graphics.Paint, float, float);
|
||||
|
||||
@@ -295,6 +295,14 @@ package android.database.sqlite {
|
||||
|
||||
}
|
||||
|
||||
package android.graphics {
|
||||
|
||||
public final class ImageDecoder implements java.lang.AutoCloseable {
|
||||
method public static android.graphics.ImageDecoder.Source createSource(android.content.res.Resources, java.io.InputStream, int);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package android.graphics.drawable {
|
||||
|
||||
public class AdaptiveIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
|
||||
|
||||
@@ -669,7 +669,6 @@ Landroid/graphics/GraphicBuffer;->CREATOR:Landroid/os/Parcelable$Creator;
|
||||
Landroid/graphics/GraphicBuffer;-><init>(IIIIJ)V
|
||||
Landroid/graphics/GraphicBuffer;->mNativeObject:J
|
||||
Landroid/graphics/ImageDecoder;-><init>(JIIZ)V
|
||||
Landroid/graphics/ImageDecoder;->onPartialImage(I)Z
|
||||
Landroid/graphics/ImageDecoder;->postProcessAndRelease(Landroid/graphics/Canvas;)I
|
||||
Landroid/graphics/LinearGradient;->mColors:[I
|
||||
Landroid/graphics/Matrix;->native_instance:J
|
||||
|
||||
@@ -38,48 +38,81 @@ using namespace android;
|
||||
|
||||
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();
|
||||
@@ -89,26 +122,26 @@ static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream) {
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -116,44 +149,46 @@ static jobject ImageDecoder_nCreateFd(JNIEnv* env, jobject /*clazz*/,
|
||||
// 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) {
|
||||
@@ -170,10 +205,8 @@ jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, std::unique_ptr<C
|
||||
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,
|
||||
@@ -264,11 +297,8 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
|
||||
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:
|
||||
@@ -278,12 +308,12 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
|
||||
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:
|
||||
@@ -293,24 +323,12 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
|
||||
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;
|
||||
@@ -357,11 +375,6 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
|
||||
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();
|
||||
@@ -404,7 +417,7 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
|
||||
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;
|
||||
}
|
||||
@@ -495,12 +508,12 @@ static jstring ImageDecoder_nGetMimeType(JNIEnv* env, jobject /*clazz*/, jlong n
|
||||
}
|
||||
|
||||
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 },
|
||||
@@ -516,10 +529,10 @@ int register_android_graphics_ImageDecoder(JNIEnv* env) {
|
||||
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");
|
||||
|
||||
@@ -33,6 +33,13 @@ struct ImageDecoder {
|
||||
kHardware_Allocator = 3,
|
||||
};
|
||||
|
||||
// These need to stay in sync with ImageDecoder.java's Error constants.
|
||||
enum Error {
|
||||
kSourceException = 1,
|
||||
kSourceIncomplete = 2,
|
||||
kSourceError = 3,
|
||||
};
|
||||
|
||||
// These need to stay in sync with PixelFormat.java's Format constants.
|
||||
enum PixelFormat {
|
||||
kUnknown = 0,
|
||||
|
||||
@@ -24,6 +24,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
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;
|
||||
@@ -102,7 +103,7 @@ public final class ImageDecoder implements AutoCloseable {
|
||||
|
||||
@Override
|
||||
public ImageDecoder createImageDecoder() throws IOException {
|
||||
return nCreate(mData, mOffset, mLength);
|
||||
return nCreate(mData, mOffset, mLength, this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,9 +118,9 @@ public final class ImageDecoder implements AutoCloseable {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +157,7 @@ public final class ImageDecoder implements AutoCloseable {
|
||||
throw new FileNotFoundException(mUri.toString());
|
||||
}
|
||||
|
||||
return createFromStream(is, true);
|
||||
return createFromStream(is, true, this);
|
||||
}
|
||||
|
||||
final FileDescriptor fd = assetFd.getFileDescriptor();
|
||||
@@ -166,9 +167,9 @@ public final class ImageDecoder implements AutoCloseable {
|
||||
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) {
|
||||
@@ -182,18 +183,19 @@ public final class ImageDecoder implements AutoCloseable {
|
||||
}
|
||||
|
||||
@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);
|
||||
@@ -207,12 +209,12 @@ public final class ImageDecoder implements AutoCloseable {
|
||||
|
||||
@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) {
|
||||
@@ -260,7 +262,7 @@ public final class ImageDecoder implements AutoCloseable {
|
||||
}
|
||||
InputStream is = mInputStream;
|
||||
mInputStream = null;
|
||||
return createFromStream(is, false);
|
||||
return createFromStream(is, false, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -305,7 +307,7 @@ public final class ImageDecoder implements AutoCloseable {
|
||||
}
|
||||
AssetInputStream ais = mAssetInputStream;
|
||||
mAssetInputStream = null;
|
||||
return createFromAsset(ais);
|
||||
return createFromAsset(ais, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -340,18 +342,19 @@ public final class ImageDecoder implements AutoCloseable {
|
||||
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);
|
||||
@@ -375,7 +378,7 @@ public final class ImageDecoder implements AutoCloseable {
|
||||
@Override
|
||||
public ImageDecoder createImageDecoder() throws IOException {
|
||||
InputStream is = mAssets.open(mFileName);
|
||||
return createFromAsset((AssetInputStream) is);
|
||||
return createFromAsset((AssetInputStream) is, this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,7 +391,7 @@ public final class ImageDecoder implements AutoCloseable {
|
||||
|
||||
@Override
|
||||
public ImageDecoder createImageDecoder() throws IOException {
|
||||
return createFromFile(mFile);
|
||||
return createFromFile(mFile, this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,9 +434,10 @@ public final class ImageDecoder implements AutoCloseable {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Thrown if the provided data is incomplete.
|
||||
/** @removed
|
||||
* @deprecated Subsumed by {@link #DecodeException}.
|
||||
*/
|
||||
@java.lang.Deprecated
|
||||
public static class IncompleteException extends IOException {};
|
||||
|
||||
/**
|
||||
@@ -468,10 +472,70 @@ public final class ImageDecoder implements AutoCloseable {
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@@ -486,13 +550,12 @@ public final class ImageDecoder implements AutoCloseable {
|
||||
* 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
|
||||
@@ -667,6 +730,7 @@ public final class ImageDecoder implements AutoCloseable {
|
||||
* 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);
|
||||
}
|
||||
@@ -1087,14 +1151,8 @@ public final class ImageDecoder implements AutoCloseable {
|
||||
@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);
|
||||
}
|
||||
@@ -1310,23 +1368,28 @@ public final class ImageDecoder implements AutoCloseable {
|
||||
* 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,
|
||||
|
||||
Reference in New Issue
Block a user