Unhide ImageDecoder
ImageDecoder is a new way to decode Bitmaps (or Drawables) that
streamlines common use cases of BitmapFactory.
Bug: 63909536
Bug: 63908092
Test: I0f36ce34c968fd7fae4d8edebabea3a421859615
Ib2877276da8464b5f3eef0bbb848de202c90e97e
I8d1672180d8325ae1caf44f0bbf41036b94e6253
I405ebc86f7b6b019e0f040f1d4afe2e9d4354e5d
Iaeb7c27bafb351932f0fabe59461ef50b1e2424a
Change-Id: Iee236ac73e0bc37ef6903a8150c0d2c84e5cf906
diff --git a/api/current.txt b/api/current.txt
index 3d8c5a6..edd7c5d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -13503,6 +13503,58 @@
ctor public EmbossMaskFilter(float[], float, float, float);
}
+ public final class ImageDecoder implements java.lang.AutoCloseable {
+ method public void close();
+ method public static android.graphics.ImageDecoder.Source createSource(android.content.ContentResolver, android.net.Uri);
+ method public static android.graphics.ImageDecoder.Source createSource(java.nio.ByteBuffer);
+ method public static android.graphics.ImageDecoder.Source createSource(java.io.File);
+ method public static android.graphics.Bitmap decodeBitmap(android.graphics.ImageDecoder.Source, android.graphics.ImageDecoder.OnHeaderDecodedListener) throws java.io.IOException;
+ method public static android.graphics.Bitmap decodeBitmap(android.graphics.ImageDecoder.Source) throws java.io.IOException;
+ method public static android.graphics.drawable.Drawable decodeDrawable(android.graphics.ImageDecoder.Source, android.graphics.ImageDecoder.OnHeaderDecodedListener) throws java.io.IOException;
+ method public static android.graphics.drawable.Drawable decodeDrawable(android.graphics.ImageDecoder.Source) throws java.io.IOException;
+ method public android.util.Size getSampledSize(int);
+ method public void setAllocator(int);
+ method public void setAsAlphaMask(boolean);
+ method public void setCrop(android.graphics.Rect);
+ method public void setMutable(boolean);
+ method public void setOnPartialImageListener(android.graphics.ImageDecoder.OnPartialImageListener);
+ method public void setPostProcessor(android.graphics.PostProcessor);
+ method public void setPreferRamOverQuality(boolean);
+ method public void setRequireUnpremultiplied(boolean);
+ method public void setResize(int, int);
+ method public void setResize(int);
+ field public static final int ALLOCATOR_DEFAULT = 0; // 0x0
+ field public static final int ALLOCATOR_HARDWARE = 3; // 0x3
+ field public static final int ALLOCATOR_SHARED_MEMORY = 2; // 0x2
+ field public static final int ALLOCATOR_SOFTWARE = 1; // 0x1
+ field public static final int ERROR_SOURCE_ERROR = 3; // 0x3
+ field public static final int ERROR_SOURCE_EXCEPTION = 1; // 0x1
+ field public static final int ERROR_SOURCE_INCOMPLETE = 2; // 0x2
+ }
+
+ public static abstract class ImageDecoder.Error implements java.lang.annotation.Annotation {
+ }
+
+ public static class ImageDecoder.ImageInfo {
+ method public java.lang.String getMimeType();
+ method public android.util.Size getSize();
+ }
+
+ 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);
+ }
+
+ public static abstract class ImageDecoder.Source {
+ }
+
public class ImageFormat {
ctor public ImageFormat();
method public static int getBitsPerPixel(int);
@@ -14073,6 +14125,10 @@
ctor public PorterDuffXfermode(android.graphics.PorterDuff.Mode);
}
+ public abstract interface PostProcessor {
+ method public abstract int onPostProcess(android.graphics.Canvas);
+ }
+
public class RadialGradient extends android.graphics.Shader {
ctor public RadialGradient(float, float, float, int[], float[], android.graphics.Shader.TileMode);
ctor public RadialGradient(float, float, float, int, int, android.graphics.Shader.TileMode);
diff --git a/core/jni/android/graphics/AnimatedImageDrawable.cpp b/core/jni/android/graphics/AnimatedImageDrawable.cpp
index 12feaab..ec15cce 100644
--- a/core/jni/android/graphics/AnimatedImageDrawable.cpp
+++ b/core/jni/android/graphics/AnimatedImageDrawable.cpp
@@ -58,8 +58,7 @@
SkPictureRecorder recorder;
SkCanvas* skcanvas = recorder.beginRecording(bounds);
std::unique_ptr<Canvas> canvas(Canvas::create_canvas(skcanvas));
- postProcessAndRelease(env, jpostProcess, std::move(canvas), bounds.width(),
- bounds.height());
+ postProcessAndRelease(env, jpostProcess, std::move(canvas));
if (env->ExceptionCheck()) {
return 0;
}
diff --git a/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp b/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp
index 115edd4..85c9ef3 100644
--- a/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp
+++ b/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp
@@ -67,7 +67,7 @@
}
if (!buffer) {
- return this->setPosition(mPosition + size);
+ return this->setPosition(mPosition + size) ? size : 0;
}
auto* env = get_env_or_die(mJvm);
diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp
index 1c2528f..743a7ef 100644
--- a/core/jni/android/graphics/ImageDecoder.cpp
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -37,15 +37,13 @@
using namespace android;
static jclass gImageDecoder_class;
-static jclass gPoint_class;
+static jclass gSize_class;
static jclass gIncomplete_class;
-static jclass gCorrupt_class;
static jclass gCanvas_class;
static jmethodID gImageDecoder_constructorMethodID;
static jmethodID gImageDecoder_postProcessMethodID;
-static jmethodID gPoint_constructorMethodID;
+static jmethodID gSize_constructorMethodID;
static jmethodID gIncomplete_constructorMethodID;
-static jmethodID gCorrupt_constructorMethodID;
static jmethodID gCallback_onPartialImageMethodID;
static jmethodID gCanvas_constructorMethodID;
static jmethodID gCanvas_releaseMethodID;
@@ -157,8 +155,7 @@
return native_create(env, std::move(stream));
}
-jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, std::unique_ptr<Canvas> canvas,
- int width, int height) {
+jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, std::unique_ptr<Canvas> canvas) {
jobject jcanvas = env->NewObject(gCanvas_class, gCanvas_constructorMethodID,
reinterpret_cast<jlong>(canvas.get()));
if (!jcanvas) {
@@ -169,8 +166,7 @@
// jcanvas now owns canvas.
canvas.release();
- return env->CallIntMethod(jimageDecoder, gImageDecoder_postProcessMethodID,
- jcanvas, width, height);
+ return env->CallIntMethod(jimageDecoder, gImageDecoder_postProcessMethodID, jcanvas);
}
// Note: jpostProcess points to an ImageDecoder object if it has a PostProcess object, and nullptr
@@ -271,39 +267,47 @@
if (jexception) {
env->ExceptionClear();
}
+ int onPartialImageError = jexception ? 1 // ImageDecoder.java's ERROR_SOURCE_EXCEPTION
+ : 0; // No error.
switch (result) {
case SkCodec::kSuccess:
// Ignore the exception, since the decode was successful anyway.
jexception = nullptr;
+ onPartialImageError = 0;
break;
case SkCodec::kIncompleteInput:
- if (jcallback && !jexception) {
- jexception = (jthrowable) env->NewObject(gIncomplete_class,
- gIncomplete_constructorMethodID);
+ if (!jexception) {
+ onPartialImageError = 2; // ImageDecoder.java's ERROR_SOURCE_EXCEPTION
}
break;
case SkCodec::kErrorInInput:
- if (jcallback && !jexception) {
- jexception = (jthrowable) env->NewObject(gCorrupt_class,
- gCorrupt_constructorMethodID);
+ if (!jexception) {
+ onPartialImageError = 3; // ImageDecoder.java's ERROR_SOURCE_ERROR
}
break;
default:
SkString msg;
- msg.printf("getPixels failed with error %i", result);
+ msg.printf("getPixels failed with error %s", SkCodec::ResultToString(result));
doThrowIOE(env, msg.c_str());
return nullptr;
}
- if (jexception) {
- bool throwException = !env->CallBooleanMethod(jcallback, gCallback_onPartialImageMethodID,
- jexception);
+ if (jexception || onPartialImageError) {
+ bool throwException = !jcallback ||
+ !env->CallBooleanMethod(jcallback, gCallback_onPartialImageMethodID,
+ onPartialImageError);
if (env->ExceptionCheck()) {
return nullptr;
}
if (throwException) {
- env->Throw(jexception);
+ if (jexception) {
+ env->Throw(jexception);
+ } else if (onPartialImageError == 2) {
+ env->ThrowNew(gIncomplete_class, "Incomplete input");
+ } else {
+ doThrowIOE(env, "image has an error!");
+ }
return nullptr;
}
}
@@ -399,8 +403,7 @@
if (jpostProcess) {
std::unique_ptr<Canvas> canvas(Canvas::create_canvas(bm));
- jint pixelFormat = postProcessAndRelease(env, jpostProcess, std::move(canvas),
- bm.width(), bm.height());
+ jint pixelFormat = postProcessAndRelease(env, jpostProcess, std::move(canvas));
if (env->ExceptionCheck()) {
return nullptr;
}
@@ -472,7 +475,7 @@
jint sampleSize) {
auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
SkISize size = decoder->mCodec->getSampledDimensions(sampleSize);
- return env->NewObject(gPoint_class, gPoint_constructorMethodID, size.width(), size.height());
+ return env->NewObject(gSize_class, gSize_constructorMethodID, size.width(), size.height());
}
static void ImageDecoder_nGetPadding(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
@@ -496,9 +499,9 @@
{ "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$OnPartialImageListener;Landroid/graphics/ImageDecoder;IILandroid/graphics/Rect;ZIZZZ)Landroid/graphics/Bitmap;",
+ { "nDecodeBitmap", "(JLandroid/graphics/ImageDecoder;Landroid/graphics/ImageDecoder;IILandroid/graphics/Rect;ZIZZZ)Landroid/graphics/Bitmap;",
(void*) ImageDecoder_nDecodeBitmap },
- { "nGetSampledSize","(JI)Landroid/graphics/Point;", (void*) ImageDecoder_nGetSampledSize },
+ { "nGetSampledSize","(JI)Landroid/util/Size;", (void*) ImageDecoder_nGetSampledSize },
{ "nGetPadding", "(JLandroid/graphics/Rect;)V", (void*) ImageDecoder_nGetPadding },
{ "nClose", "(J)V", (void*) ImageDecoder_nClose},
{ "nGetMimeType", "(J)Ljava/lang/String;", (void*) ImageDecoder_nGetMimeType },
@@ -507,19 +510,15 @@
int register_android_graphics_ImageDecoder(JNIEnv* env) {
gImageDecoder_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder"));
gImageDecoder_constructorMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "<init>", "(JIIZ)V");
- gImageDecoder_postProcessMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "postProcessAndRelease", "(Landroid/graphics/Canvas;II)I");
+ gImageDecoder_postProcessMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "postProcessAndRelease", "(Landroid/graphics/Canvas;)I");
- gPoint_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Point"));
- gPoint_constructorMethodID = GetMethodIDOrDie(env, gPoint_class, "<init>", "(II)V");
+ 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");
- gCorrupt_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder$CorruptException"));
- gCorrupt_constructorMethodID = GetMethodIDOrDie(env, gCorrupt_class, "<init>", "()V");
-
- jclass callback_class = FindClassOrDie(env, "android/graphics/ImageDecoder$OnPartialImageListener");
- gCallback_onPartialImageMethodID = GetMethodIDOrDie(env, callback_class, "onPartialImage", "(Ljava/io/IOException;)Z");
+ gCallback_onPartialImageMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "onPartialImage", "(I)Z");
gCanvas_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Canvas"));
gCanvas_constructorMethodID = GetMethodIDOrDie(env, gCanvas_class, "<init>", "(J)V");
diff --git a/core/jni/android/graphics/ImageDecoder.h b/core/jni/android/graphics/ImageDecoder.h
index 2df71eb..5d7e676 100644
--- a/core/jni/android/graphics/ImageDecoder.h
+++ b/core/jni/android/graphics/ImageDecoder.h
@@ -51,5 +51,4 @@
// Creates a Java Canvas object from canvas, calls jimageDecoder's PostProcess on it, and then
// releases the Canvas.
// Caller needs to check for exceptions.
-jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, std::unique_ptr<Canvas> canvas,
- int width, int height);
+jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, std::unique_ptr<Canvas> canvas);
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 4d715d1..02d22be 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -16,6 +16,7 @@
package android.graphics;
+import static android.system.OsConstants.SEEK_CUR;
import static android.system.OsConstants.SEEK_SET;
import android.annotation.IntDef;
@@ -31,6 +32,7 @@
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.NinePatchDrawable;
import android.net.Uri;
+import android.util.Size;
import android.system.ErrnoException;
import android.system.Os;
import android.util.DisplayMetrics;
@@ -40,6 +42,7 @@
import dalvik.system.CloseGuard;
import java.nio.ByteBuffer;
+import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -55,14 +58,16 @@
/**
* Class for decoding images as {@link Bitmap}s or {@link Drawable}s.
- * @hide
*/
public final class ImageDecoder implements AutoCloseable {
/**
* Source of the encoded image data.
*/
public static abstract class Source {
+ private Source() {}
+
/* @hide */
+ @Nullable
Resources getResources() { return null; }
/* @hide */
@@ -79,11 +84,12 @@
}
/* @hide */
+ @NonNull
abstract ImageDecoder createImageDecoder() throws IOException;
};
private static class ByteArraySource extends Source {
- ByteArraySource(byte[] data, int offset, int length) {
+ ByteArraySource(@NonNull byte[] data, int offset, int length) {
mData = data;
mOffset = offset;
mLength = length;
@@ -99,7 +105,7 @@
}
private static class ByteBufferSource extends Source {
- ByteBufferSource(ByteBuffer buffer) {
+ ByteBufferSource(@NonNull ByteBuffer buffer) {
mBuffer = buffer;
}
private final ByteBuffer mBuffer;
@@ -116,7 +122,7 @@
}
private static class ContentResolverSource extends Source {
- ContentResolverSource(ContentResolver resolver, Uri uri) {
+ ContentResolverSource(@NonNull ContentResolver resolver, @NonNull Uri uri) {
mResolver = resolver;
mUri = uri;
}
@@ -167,7 +173,31 @@
}
}
- private static ImageDecoder createFromStream(InputStream is) throws IOException {
+ @NonNull
+ private static ImageDecoder createFromFile(@NonNull File file) throws IOException {
+ FileInputStream stream = new FileInputStream(file);
+ FileDescriptor fd = stream.getFD();
+ try {
+ Os.lseek(fd, 0, SEEK_CUR);
+ } catch (ErrnoException e) {
+ return createFromStream(stream);
+ }
+
+ ImageDecoder decoder = null;
+ try {
+ decoder = nCreate(fd);
+ } finally {
+ if (decoder == null) {
+ IoUtils.closeQuietly(stream);
+ } else {
+ decoder.mInputStream = stream;
+ }
+ }
+ return decoder;
+ }
+
+ @NonNull
+ private static ImageDecoder createFromStream(@NonNull InputStream is) throws IOException {
// Arbitrary size matches BitmapFactory.
byte[] storage = new byte[16 * 1024];
ImageDecoder decoder = null;
@@ -220,7 +250,7 @@
}
private static class ResourceSource extends Source {
- ResourceSource(Resources res, int resId) {
+ ResourceSource(@NonNull Resources res, int resId) {
mResources = res;
mResId = resId;
mResDensity = Bitmap.DENSITY_NONE;
@@ -269,59 +299,54 @@
}
}
+ private static class FileSource extends Source {
+ FileSource(@NonNull File file) {
+ mFile = file;
+ }
+
+ private final File mFile;
+
+ @Override
+ public ImageDecoder createImageDecoder() throws IOException {
+ return createFromFile(mFile);
+ }
+ }
+
/**
* Contains information about the encoded image.
*/
public static class ImageInfo {
- /**
- * Width of the image, without scaling or cropping.
- */
- public final int width;
+ private final Size mSize;
+ private ImageDecoder mDecoder;
- /**
- * Height of the image, without scaling or cropping.
- */
- public final int height;
-
- /* @hide */
- ImageDecoder decoder;
-
- /* @hide */
- ImageInfo(ImageDecoder decoder) {
- this.width = decoder.mWidth;
- this.height = decoder.mHeight;
- this.decoder = decoder;
+ private ImageInfo(@NonNull ImageDecoder decoder) {
+ mSize = new Size(decoder.mWidth, decoder.mHeight);
+ mDecoder = decoder;
}
/**
- * The mimeType of the image, if known.
+ * Size of the image, without scaling or cropping.
*/
+ @NonNull
+ public Size getSize() {
+ return mSize;
+ }
+
+ /**
+ * The mimeType of the image.
+ */
+ @NonNull
public String getMimeType() {
- return decoder.getMimeType();
+ return mDecoder.getMimeType();
}
};
/**
- * Used if the provided data is incomplete.
- *
- * May be thrown if there is nothing to display.
- *
- * If supplied to onPartialImage, there may be a correct partial image to
- * display.
+ * Thrown if the provided data is incomplete.
*/
public static class IncompleteException extends IOException {};
/**
- * Used if the provided data is corrupt.
- *
- * May be thrown if there is nothing to display.
- *
- * If supplied to onPartialImage, there may be a correct partial image to
- * display.
- */
- public static class CorruptException extends IOException {};
-
- /**
* Optional listener supplied to {@link #decodeDrawable} or
* {@link #decodeBitmap}.
*/
@@ -329,31 +354,55 @@
/**
* Called when the header is decoded and the size is known.
*
- * @param info Information about the encoded image.
* @param decoder allows changing the default settings of the decode.
+ * @param info Information about the encoded image.
+ * @param source that created the decoder.
*/
- public void onHeaderDecoded(ImageInfo info, ImageDecoder decoder);
+ public void onHeaderDecoded(@NonNull ImageDecoder decoder,
+ @NonNull ImageInfo info, @NonNull Source source);
};
/**
+ * An Exception was thrown reading the {@link Source}.
+ */
+ public static final int ERROR_SOURCE_EXCEPTION = 1;
+
+ /**
+ * The encoded data was incomplete.
+ */
+ public static final int ERROR_SOURCE_INCOMPLETE = 2;
+
+ /**
+ * The encoded data contained an error.
+ */
+ public static final int ERROR_SOURCE_ERROR = 3;
+
+ @Retention(SOURCE)
+ @IntDef({ ERROR_SOURCE_EXCEPTION, ERROR_SOURCE_INCOMPLETE, ERROR_SOURCE_ERROR })
+ public @interface Error {};
+
+ /**
* Optional listener supplied to the ImageDecoder.
+ *
+ * Without this listener, errors will throw {@link java.io.IOException}.
*/
public static interface OnPartialImageListener {
/**
* Called when there is only a partial image to display.
*
- * If the input is incomplete or contains an error, this listener lets
- * the client know that and allows them to optionally bypass the rest
- * of the decode/creation process.
+ * If decoding is interrupted after having decoded a partial image,
+ * this listener lets the client know that and allows them to
+ * optionally finish the rest of the decode/creation process to create
+ * a partial {@link Drawable}/{@link Bitmap}.
*
- * @param e IOException containing information about the error that
- * interrupted the decode.
- * @return True (which is the default) to create and return a
- * {@link Drawable}/{@link Bitmap} with partial data. False to
- * abort the decode and throw the {@link java.io.IOException}.
+ * @param error indicating what interrupted the decode.
+ * @param source that had the error.
+ * @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}.
*/
- public boolean onPartialImage(IOException e);
+ public boolean onPartialImage(@Error int error, @NonNull Source source);
};
// Fields
@@ -364,14 +413,15 @@
private int mDesiredWidth;
private int mDesiredHeight;
- private int mAllocator = DEFAULT_ALLOCATOR;
+ private int mAllocator = ALLOCATOR_DEFAULT;
private boolean mRequireUnpremultiplied = false;
private boolean mMutable = false;
private boolean mPreferRamOverQuality = false;
private boolean mAsAlphaMask = false;
private Rect mCropRect;
+ private Source mSource;
- private PostProcess mPostProcess;
+ private PostProcessor mPostProcessor;
private OnPartialImageListener mOnPartialImageListener;
// Objects for interacting with the input.
@@ -412,6 +462,7 @@
/**
* Create a new {@link Source} from an asset.
+ * @hide
*
* @param res the {@link Resources} object containing the image data.
* @param resId resource ID of the image data.
@@ -441,6 +492,7 @@
/**
* Create a new {@link Source} from a byte array.
+ *
* @param data byte array of compressed image data.
* @param offset offset into data for where the decoder should begin
* parsing.
@@ -448,7 +500,9 @@
* @throws NullPointerException if data is null.
* @throws ArrayIndexOutOfBoundsException if offset and length are
* not within data.
+ * @hide
*/
+ @NonNull
public static Source createSource(@NonNull byte[] data, int offset,
int length) throws ArrayIndexOutOfBoundsException {
if (data == null) {
@@ -464,7 +518,9 @@
/**
* See {@link #createSource(byte[], int, int).
+ * @hide
*/
+ @NonNull
public static Source createSource(@NonNull byte[] data) {
return createSource(data, 0, data.length);
}
@@ -472,13 +528,15 @@
/**
* Create a new {@link Source} from a {@link java.nio.ByteBuffer}.
*
- * The returned {@link Source} effectively takes ownership of the
+ * <p>The returned {@link Source} effectively takes ownership of the
* {@link java.nio.ByteBuffer}; i.e. no other code should modify it after
- * this call.
+ * this call.</p>
*
- * Decoding will start from {@link java.nio.ByteBuffer#position()}.
+ * Decoding will start from {@link java.nio.ByteBuffer#position()}. The
+ * position after decoding is undefined.
*/
- public static Source createSource(ByteBuffer buffer) {
+ @NonNull
+ public static Source createSource(@NonNull ByteBuffer buffer) {
return new ByteBufferSource(buffer);
}
@@ -499,20 +557,29 @@
}
/**
+ * Create a new {@link Source} from a {@link java.io.File}.
+ */
+ @NonNull
+ public static Source createSource(@NonNull File file) {
+ return new FileSource(file);
+ }
+
+ /**
* Return the width and height of a given sample size.
*
- * This takes an input that functions like
+ * <p>This takes an input that functions like
* {@link BitmapFactory.Options#inSampleSize}. It returns a width and
* height that can be acheived by sampling the encoded image. Other widths
* and heights may be supported, but will require an additional (internal)
* scaling step. Such internal scaling is *not* supported with
- * {@link #requireUnpremultiplied}.
+ * {@link #setRequireUnpremultiplied} set to {@code true}.</p>
*
* @param sampleSize Sampling rate of the encoded image.
- * @return Point {@link Point#x} and {@link Point#y} correspond to the
- * width and height after sampling.
+ * @return {@link android.util.Size} of the width and height after
+ * sampling.
*/
- public Point getSampledSize(int sampleSize) {
+ @NonNull
+ public Size getSampledSize(int sampleSize) {
if (sampleSize <= 0) {
throw new IllegalArgumentException("sampleSize must be positive! "
+ "provided " + sampleSize);
@@ -531,7 +598,7 @@
* @param width must be greater than 0.
* @param height must be greater than 0.
*/
- public void resize(int width, int height) {
+ public void setResize(int width, int height) {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("Dimensions must be positive! "
+ "provided (" + width + ", " + height + ")");
@@ -544,14 +611,14 @@
/**
* Resize based on a sample size.
*
- * This has the same effect as passing the result of
- * {@link #getSampledSize} to {@link #resize(int, int)}.
+ * <p>This has the same effect as passing the result of
+ * {@link #getSampledSize} to {@link #setResize(int, int)}.</p>
*
* @param sampleSize Sampling rate of the encoded image.
*/
- public void resize(int sampleSize) {
- Point dimensions = this.getSampledSize(sampleSize);
- this.resize(dimensions.x, dimensions.y);
+ public void setResize(int sampleSize) {
+ Size size = this.getSampledSize(sampleSize);
+ this.setResize(size.getWidth(), size.getHeight());
}
private boolean requestedResize() {
@@ -567,7 +634,7 @@
* switch to software when HARDWARE is incompatible, e.g.
* {@link #setMutable}, {@link #setAsAlphaMask}.
*/
- public static final int DEFAULT_ALLOCATOR = 0;
+ public static final int ALLOCATOR_DEFAULT = 0;
/**
* Use a software allocation for the pixel memory.
@@ -575,28 +642,29 @@
* Useful for drawing to a software {@link Canvas} or for
* accessing the pixels on the final output.
*/
- public static final int SOFTWARE_ALLOCATOR = 1;
+ public static final int ALLOCATOR_SOFTWARE = 1;
/**
* Use shared memory for the pixel memory.
*
* Useful for sharing across processes.
*/
- public static final int SHARED_MEMORY_ALLOCATOR = 2;
+ public static final int ALLOCATOR_SHARED_MEMORY = 2;
/**
* Require a {@link Bitmap.Config#HARDWARE} {@link Bitmap}.
*
- * This will throw an {@link java.lang.IllegalStateException} when combined
- * with incompatible options, like {@link #setMutable} or
- * {@link #setAsAlphaMask}.
+ * When this is combined with incompatible options, like
+ * {@link #setMutable} or {@link #setAsAlphaMask}, {@link #decodeDrawable}
+ * / {@link #decodeBitmap} will throw an
+ * {@link java.lang.IllegalStateException}.
*/
- public static final int HARDWARE_ALLOCATOR = 3;
+ public static final int ALLOCATOR_HARDWARE = 3;
/** @hide **/
@Retention(SOURCE)
- @IntDef({ DEFAULT_ALLOCATOR, SOFTWARE_ALLOCATOR, SHARED_MEMORY_ALLOCATOR,
- HARDWARE_ALLOCATOR })
+ @IntDef({ ALLOCATOR_DEFAULT, ALLOCATOR_SOFTWARE, ALLOCATOR_SHARED_MEMORY,
+ ALLOCATOR_HARDWARE })
public @interface Allocator {};
/**
@@ -604,47 +672,46 @@
*
* This is ignored for animated drawables.
*
- * TODO: Allow accessing the backing from the Bitmap.
- *
* @param allocator Type of allocator to use.
*/
public void setAllocator(@Allocator int allocator) {
- if (allocator < DEFAULT_ALLOCATOR || allocator > HARDWARE_ALLOCATOR) {
+ if (allocator < ALLOCATOR_DEFAULT || allocator > ALLOCATOR_HARDWARE) {
throw new IllegalArgumentException("invalid allocator " + allocator);
}
mAllocator = allocator;
}
/**
- * Create a {@link Bitmap} with unpremultiplied pixels.
+ * Specify whether the {@link Bitmap} should have unpremultiplied pixels.
*
* By default, ImageDecoder will create a {@link Bitmap} with
* premultiplied pixels, which is required for drawing with the
* {@link android.view.View} system (i.e. to a {@link Canvas}). Calling
- * this method will result in {@link #decodeBitmap} returning a
- * {@link Bitmap} with unpremultiplied pixels. See
- * {@link Bitmap#isPremultiplied}. Incompatible with
+ * this method with a value of {@code true} will result in
+ * {@link #decodeBitmap} returning a {@link Bitmap} with unpremultiplied
+ * pixels. See {@link Bitmap#isPremultiplied}. This is incompatible with
* {@link #decodeDrawable}; attempting to decode an unpremultiplied
* {@link Drawable} will throw an {@link java.lang.IllegalStateException}.
*/
- public void requireUnpremultiplied() {
- mRequireUnpremultiplied = true;
+ public void setRequireUnpremultiplied(boolean requireUnpremultiplied) {
+ mRequireUnpremultiplied = requireUnpremultiplied;
}
/**
* Modify the image after decoding and scaling.
*
- * This allows adding effects prior to returning a {@link Drawable} or
+ * <p>This allows adding effects prior to returning a {@link Drawable} or
* {@link Bitmap}. For a {@code Drawable} or an immutable {@code Bitmap},
- * this is the only way to process the image after decoding.
+ * this is the only way to process the image after decoding.</p>
*
- * If set on a nine-patch image, the nine-patch data is ignored.
+ * <p>If set on a nine-patch image, the nine-patch data is ignored.</p>
*
- * For an animated image, the drawing commands drawn on the {@link Canvas}
- * will be recorded immediately and then applied to each frame.
+ * <p>For an animated image, the drawing commands drawn on the
+ * {@link Canvas} will be recorded immediately and then applied to each
+ * frame.</p>
*/
- public void setPostProcess(PostProcess p) {
- mPostProcess = p;
+ public void setPostProcessor(@Nullable PostProcessor p) {
+ mPostProcessor = p;
}
/**
@@ -653,66 +720,72 @@
* Will be called if there is an error in the input. Without one, a
* partial {@link Bitmap} will be created.
*/
- public void setOnPartialImageListener(OnPartialImageListener l) {
+ public void setOnPartialImageListener(@Nullable OnPartialImageListener l) {
mOnPartialImageListener = l;
}
/**
* Crop the output to {@code subset} of the (possibly) scaled image.
*
- * {@code subset} must be contained within the size set by {@link #resize}
- * or the bounds of the image if resize was not called. Otherwise an
- * {@link IllegalStateException} will be thrown.
+ * <p>{@code subset} must be contained within the size set by
+ * {@link #setResize} or the bounds of the image if setResize was not
+ * called. Otherwise an {@link IllegalStateException} will be thrown by
+ * {@link #decodeDrawable}/{@link #decodeBitmap}.</p>
*
- * NOT intended as a replacement for
+ * <p>NOT intended as a replacement for
* {@link BitmapRegionDecoder#decodeRegion}. This supports all formats,
- * but merely crops the output.
+ * but merely crops the output.</p>
*/
- public void crop(Rect subset) {
+ public void setCrop(@Nullable Rect subset) {
mCropRect = subset;
}
/**
- * Create a mutable {@link Bitmap}.
+ * Specify whether the {@link Bitmap} should be mutable.
*
- * By default, a {@link Bitmap} created will be immutable, but that can be
- * changed with this call.
+ * <p>By default, a {@link Bitmap} created will be immutable, but that can
+ * be changed with this call.</p>
*
- * Incompatible with {@link #HARDWARE_ALLOCATOR}, because
- * {@link Bitmap.Config#HARDWARE} Bitmaps cannot be mutable. Attempting to
- * combine them will throw an {@link java.lang.IllegalStateException}.
+ * <p>Mutable Bitmaps are incompatible with {@link #ALLOCATOR_HARDWARE},
+ * because {@link Bitmap.Config#HARDWARE} Bitmaps cannot be mutable.
+ * Attempting to combine them will throw an
+ * {@link java.lang.IllegalStateException}.</p>
*
- * Incompatible with {@link #decodeDrawable}, which would require
- * retrieving the Bitmap from the returned Drawable in order to modify.
- * Attempting to decode a mutable {@link Drawable} will throw an
- * {@link java.lang.IllegalStateException}
+ * <p>Mutable Bitmaps are also incompatible with {@link #decodeDrawable},
+ * which would require retrieving the Bitmap from the returned Drawable in
+ * order to modify. Attempting to decode a mutable {@link Drawable} will
+ * throw an {@link java.lang.IllegalStateException}.</p>
*/
- public void setMutable() {
- mMutable = true;
+ public void setMutable(boolean mutable) {
+ mMutable = mutable;
}
/**
- * Potentially save RAM at the expense of quality.
+ * Specify whether to potentially save RAM at the expense of quality.
*
- * This may result in a {@link Bitmap} with a denser {@link Bitmap.Config},
- * depending on the image. For example, for an opaque {@link Bitmap}, this
- * may result in a {@link Bitmap.Config} with no alpha information.
+ * Setting this to {@code true} may result in a {@link Bitmap} with a
+ * denser {@link Bitmap.Config}, depending on the image. For example, for
+ * an opaque {@link Bitmap}, this may result in a {@link Bitmap.Config}
+ * with no alpha information.
*/
- public void setPreferRamOverQuality() {
- mPreferRamOverQuality = true;
+ public void setPreferRamOverQuality(boolean preferRamOverQuality) {
+ mPreferRamOverQuality = preferRamOverQuality;
}
/**
- * Potentially treat the output as an alpha mask.
+ * Specify whether to potentially treat the output as an alpha mask.
*
- * If the image is encoded in a format with only one channel, treat that
- * channel as alpha. Otherwise this call has no effect.
+ * <p>If this is set to {@code true} and the image is encoded in a format
+ * with only one channel, treat that channel as alpha. Otherwise this call has
+ * no effect.</p>
*
- * Incompatible with {@link #HARDWARE_ALLOCATOR}. Trying to combine them
- * will throw an {@link java.lang.IllegalStateException}.
+ * <p>setAsAlphaMask is incompatible with {@link #ALLOCATOR_HARDWARE}. Trying to
+ * combine them will result in {@link #decodeDrawable}/
+ * {@link #decodeBitmap} throwing an
+ * {@link java.lang.IllegalStateException}.</p>
*/
- public void setAsAlphaMask() {
- mAsAlphaMask = true;
+ public void setAsAlphaMask(boolean asAlphaMask) {
+ mAsAlphaMask = asAlphaMask;
}
@Override
@@ -739,7 +812,7 @@
checkSubset(mDesiredWidth, mDesiredHeight, mCropRect);
- if (mAllocator == HARDWARE_ALLOCATOR) {
+ if (mAllocator == ALLOCATOR_HARDWARE) {
if (mMutable) {
throw new IllegalStateException("Cannot make mutable HARDWARE Bitmap!");
}
@@ -748,7 +821,7 @@
}
}
- if (mPostProcess != null && mRequireUnpremultiplied) {
+ if (mPostProcessor != null && mRequireUnpremultiplied) {
throw new IllegalStateException("Cannot draw to unpremultiplied pixels!");
}
}
@@ -763,18 +836,34 @@
}
}
+ @NonNull
private Bitmap decodeBitmap() throws IOException {
checkState();
- // nDecodeBitmap calls postProcessAndRelease only if mPostProcess
+ // nDecodeBitmap calls onPartialImage only if mOnPartialImageListener
+ // exists
+ ImageDecoder partialImagePtr = mOnPartialImageListener == null ? null : this;
+ // nDecodeBitmap calls postProcessAndRelease only if mPostProcessor
// exists.
- ImageDecoder postProcessPtr = mPostProcess == null ? null : this;
- return nDecodeBitmap(mNativePtr, mOnPartialImageListener,
+ ImageDecoder postProcessPtr = mPostProcessor == null ? null : this;
+ return nDecodeBitmap(mNativePtr, partialImagePtr,
postProcessPtr, mDesiredWidth, mDesiredHeight, mCropRect,
mMutable, mAllocator, mRequireUnpremultiplied,
mPreferRamOverQuality, mAsAlphaMask);
}
+ private void callHeaderDecoded(@Nullable OnHeaderDecodedListener listener,
+ @NonNull Source src) {
+ if (listener != null) {
+ ImageInfo info = new ImageInfo(this);
+ try {
+ listener.onHeaderDecoded(this, info, src);
+ } finally {
+ info.mDecoder = null;
+ }
+ }
+ }
+
/**
* Create a {@link Drawable} from a {@code Source}.
*
@@ -791,14 +880,8 @@
public static Drawable decodeDrawable(@NonNull Source src,
@Nullable OnHeaderDecodedListener listener) throws IOException {
try (ImageDecoder decoder = src.createImageDecoder()) {
- if (listener != null) {
- ImageInfo info = new ImageInfo(decoder);
- try {
- listener.onHeaderDecoded(info, decoder);
- } finally {
- info.decoder = null;
- }
- }
+ decoder.mSource = src;
+ decoder.callHeaderDecoded(listener, src);
if (decoder.mRequireUnpremultiplied) {
// Though this could be supported (ignored) for opaque images,
@@ -817,8 +900,8 @@
final int srcDensity = computeDensity(src, decoder);
if (decoder.mAnimated) {
// AnimatedImageDrawable calls postProcessAndRelease only if
- // mPostProcess exists.
- ImageDecoder postProcessPtr = decoder.mPostProcess == null ?
+ // mPostProcessor exists.
+ ImageDecoder postProcessPtr = decoder.mPostProcessor == null ?
null : decoder;
Drawable d = new AnimatedImageDrawable(decoder.mNativePtr,
postProcessPtr, decoder.mDesiredWidth,
@@ -878,14 +961,8 @@
public static Bitmap decodeBitmap(@NonNull Source src,
@Nullable OnHeaderDecodedListener listener) throws IOException {
try (ImageDecoder decoder = src.createImageDecoder()) {
- if (listener != null) {
- ImageInfo info = new ImageInfo(decoder);
- try {
- listener.onHeaderDecoded(info, decoder);
- } finally {
- info.decoder = null;
- }
- }
+ decoder.mSource = src;
+ decoder.callHeaderDecoded(listener, src);
// this call potentially manipulates the decoder so it must be performed prior to
// decoding the bitmap
@@ -922,13 +999,14 @@
float scale = (float) dstDensity / srcDensity;
int scaledWidth = (int) (decoder.mWidth * scale + 0.5f);
int scaledHeight = (int) (decoder.mHeight * scale + 0.5f);
- decoder.resize(scaledWidth, scaledHeight);
+ decoder.setResize(scaledWidth, scaledHeight);
return dstDensity;
}
return srcDensity;
}
+ @NonNull
private String getMimeType() {
return nGetMimeType(mNativePtr);
}
@@ -945,14 +1023,22 @@
* Private method called by JNI.
*/
@SuppressWarnings("unused")
- private int postProcessAndRelease(@NonNull Canvas canvas, int width, int height) {
+ private int postProcessAndRelease(@NonNull Canvas canvas) {
try {
- return mPostProcess.postProcess(canvas, width, height);
+ return mPostProcessor.onPostProcess(canvas);
} finally {
canvas.release();
}
}
+ /**
+ * Private method called by JNI.
+ */
+ @SuppressWarnings("unused")
+ private boolean onPartialImage(@Error int error) {
+ return mOnPartialImageListener.onPartialImage(error, mSource);
+ }
+
private static native ImageDecoder nCreate(long asset) throws IOException;
private static native ImageDecoder nCreate(ByteBuffer buffer,
int position,
@@ -960,19 +1046,20 @@
private static native ImageDecoder nCreate(byte[] data, int offset,
int length) throws IOException;
private static native ImageDecoder nCreate(InputStream is, byte[] storage);
+ // The fd must be seekable.
private static native ImageDecoder nCreate(FileDescriptor fd) throws IOException;
@NonNull
private static native Bitmap nDecodeBitmap(long nativePtr,
- OnPartialImageListener listener,
- @Nullable ImageDecoder decoder, // Only used if mPostProcess != null
+ @Nullable ImageDecoder partialImageListener,
+ @Nullable ImageDecoder postProcessor,
int width, int height,
- Rect cropRect, boolean mutable,
+ @Nullable Rect cropRect, boolean mutable,
int allocator, boolean requireUnpremul,
boolean preferRamOverQuality, boolean asAlphaMask)
throws IOException;
- private static native Point nGetSampledSize(long nativePtr,
- int sampleSize);
- private static native void nGetPadding(long nativePtr, Rect outRect);
+ private static native Size nGetSampledSize(long nativePtr,
+ int sampleSize);
+ private static native void nGetPadding(long nativePtr, @NonNull Rect outRect);
private static native void nClose(long nativePtr);
private static native String nGetMimeType(long nativePtr);
}
diff --git a/graphics/java/android/graphics/PostProcess.java b/graphics/java/android/graphics/PostProcessor.java
similarity index 78%
rename from graphics/java/android/graphics/PostProcess.java
rename to graphics/java/android/graphics/PostProcessor.java
index c5a31e8..b1712e9 100644
--- a/graphics/java/android/graphics/PostProcess.java
+++ b/graphics/java/android/graphics/PostProcessor.java
@@ -20,38 +20,38 @@
import android.annotation.NonNull;
import android.graphics.drawable.Drawable;
-
/**
* Helper interface for adding custom processing to an image.
*
- * The image being processed may be a {@link Drawable}, {@link Bitmap} or frame
+ * <p>The image being processed may be a {@link Drawable}, {@link Bitmap} or frame
* of an animated image produced by {@link ImageDecoder}. This is called before
- * the requested object is returned.
+ * the requested object is returned.</p>
*
- * This custom processing also applies to image types that are otherwise
- * immutable, such as {@link Bitmap.Config#HARDWARE}.
+ * <p>This custom processing also applies to image types that are otherwise
+ * immutable, such as {@link Bitmap.Config#HARDWARE}.</p>
*
- * On an animated image, the callback will only be called once, but the drawing
+ * <p>On an animated image, the callback will only be called once, but the drawing
* commands will be applied to each frame, as if the {@code Canvas} had been
- * returned by {@link Picture#beginRecording}.
+ * returned by {@link Picture#beginRecording}.<p>
*
- * Supplied to ImageDecoder via {@link ImageDecoder#setPostProcess}.
- * @hide
+ * <p>Supplied to ImageDecoder via {@link ImageDecoder#setPostProcessor}.</p>
*/
-public interface PostProcess {
+public interface PostProcessor {
/**
* Do any processing after (for example) decoding.
*
- * Drawing to the {@link Canvas} will behave as if the initial processing
+ * <p>Drawing to the {@link Canvas} will behave as if the initial processing
* (e.g. decoding) already exists in the Canvas. An implementation can draw
* effects on top of this, or it can even draw behind it using
* {@link PorterDuff.Mode#DST_OVER}. A common effect is to add transparency
* to the corners to achieve rounded corners. That can be done with the
- * following code:
+ * following code:</p>
*
* <code>
* Path path = new Path();
* path.setFillType(Path.FillType.INVERSE_EVEN_ODD);
+ * int width = canvas.getWidth();
+ * int height = canvas.getHeight();
* path.addRoundRect(0, 0, width, height, 20, 20, Path.Direction.CW);
* Paint paint = new Paint();
* paint.setAntiAlias(true);
@@ -63,10 +63,6 @@
*
*
* @param canvas The {@link Canvas} to draw to.
- * @param width Width of {@code canvas}. Anything drawn outside of this
- * will be ignored.
- * @param height Height of {@code canvas}. Anything drawn outside of this
- * will be ignored.
* @return Opacity of the result after drawing.
* {@link PixelFormat#UNKNOWN} means that the implementation did not
* change whether the image has alpha. Return this unless you added
@@ -87,5 +83,5 @@
* {@link java.lang.IllegalArgumentException}.
*/
@PixelFormat.Opacity
- public int postProcess(@NonNull Canvas canvas, int width, int height);
+ public int onPostProcess(@NonNull Canvas canvas);
}
diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index f74c39d..3a5f7b7 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -165,8 +165,8 @@
Bitmap bitmap = null;
try (FileInputStream stream = new FileInputStream(filepath)) {
bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(res, stream),
- (info, decoder) -> {
- decoder.setAllocator(ImageDecoder.SOFTWARE_ALLOCATOR);
+ (decoder, info, src) -> {
+ decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
});
} catch (Exception e) {
/* do nothing. This matches the behavior of BitmapFactory.decodeFile()
@@ -198,8 +198,8 @@
Bitmap bitmap = null;
try {
bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(res, is),
- (info, decoder) -> {
- decoder.setAllocator(ImageDecoder.SOFTWARE_ALLOCATOR);
+ (decoder, info, src) -> {
+ decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
});
} catch (Exception e) {
/* do nothing. This matches the behavior of BitmapFactory.decodeStream()
@@ -838,8 +838,8 @@
Bitmap bitmap = null;
try (InputStream is = r.openRawResource(srcResId, value)) {
ImageDecoder.Source source = ImageDecoder.createSource(r, is, density);
- bitmap = ImageDecoder.decodeBitmap(source, (info, decoder) -> {
- decoder.setAllocator(ImageDecoder.SOFTWARE_ALLOCATOR);
+ bitmap = ImageDecoder.decodeBitmap(source, (decoder, info, src) -> {
+ decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
});
} catch (Exception e) {
// Do nothing and pick up the error below.
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 291b0a0..36a4d26 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -1229,8 +1229,8 @@
source = ImageDecoder.createSource(res, is);
}
- return ImageDecoder.decodeDrawable(source, (info, decoder) -> {
- decoder.setAllocator(ImageDecoder.SOFTWARE_ALLOCATOR);
+ return ImageDecoder.decodeDrawable(source, (decoder, info, src) -> {
+ decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
});
} catch (IOException e) {
/* do nothing.