Merge changes Ie2ec98d9,I81ce2bef
* changes:
Make ImageDecoder return animated Drawables
Ensure that PostProcess Canvas is released
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 77d0617..543acc7 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -119,6 +119,7 @@
"android_util_jar_StrictJarFile.cpp",
"android_graphics_Canvas.cpp",
"android_graphics_Picture.cpp",
+ "android/graphics/AnimatedImageDrawable.cpp",
"android/graphics/Bitmap.cpp",
"android/graphics/BitmapFactory.cpp",
"android/graphics/ByteBufferStreamAdaptor.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 068197a..aa9a824 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -63,6 +63,7 @@
extern int register_android_graphics_GraphicBuffer(JNIEnv* env);
extern int register_android_graphics_Graphics(JNIEnv* env);
extern int register_android_graphics_ImageDecoder(JNIEnv*);
+extern int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv*);
extern int register_android_graphics_Interpolator(JNIEnv* env);
extern int register_android_graphics_MaskFilter(JNIEnv* env);
extern int register_android_graphics_Movie(JNIEnv* env);
@@ -1396,6 +1397,7 @@
REG_JNI(register_android_graphics_FontFamily),
REG_JNI(register_android_graphics_GraphicBuffer),
REG_JNI(register_android_graphics_ImageDecoder),
+ REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable),
REG_JNI(register_android_graphics_Interpolator),
REG_JNI(register_android_graphics_MaskFilter),
REG_JNI(register_android_graphics_Matrix),
diff --git a/core/jni/android/graphics/AnimatedImageDrawable.cpp b/core/jni/android/graphics/AnimatedImageDrawable.cpp
new file mode 100644
index 0000000..12feaab
--- /dev/null
+++ b/core/jni/android/graphics/AnimatedImageDrawable.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "GraphicsJNI.h"
+#include "ImageDecoder.h"
+#include "core_jni_helpers.h"
+
+#include <hwui/Canvas.h>
+#include <SkAndroidCodec.h>
+#include <SkAnimatedImage.h>
+#include <SkColorFilter.h>
+#include <SkPicture.h>
+#include <SkPictureRecorder.h>
+
+using namespace android;
+
+struct AnimatedImageDrawable {
+ sk_sp<SkAnimatedImage> mDrawable;
+ SkPaint mPaint;
+};
+
+// Note: jpostProcess holds a handle to the ImageDecoder.
+static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/,
+ jlong nativeImageDecoder, jobject jpostProcess,
+ jint width, jint height, jobject jsubset) {
+ if (nativeImageDecoder == 0) {
+ doThrowIOE(env, "Cannot create AnimatedImageDrawable from null!");
+ return 0;
+ }
+
+ auto* imageDecoder = reinterpret_cast<ImageDecoder*>(nativeImageDecoder);
+ auto info = imageDecoder->mCodec->getInfo();
+ const SkISize scaledSize = SkISize::Make(width, height);
+ SkIRect subset;
+ if (jsubset) {
+ GraphicsJNI::jrect_to_irect(env, jsubset, &subset);
+ } else {
+ subset = SkIRect::MakeWH(width, height);
+ }
+
+ sk_sp<SkPicture> picture;
+ if (jpostProcess) {
+ SkRect bounds = SkRect::MakeWH(subset.width(), subset.height());
+
+ 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());
+ if (env->ExceptionCheck()) {
+ return 0;
+ }
+ picture = recorder.finishRecordingAsPicture();
+ }
+
+ std::unique_ptr<AnimatedImageDrawable> drawable(new AnimatedImageDrawable);
+ drawable->mDrawable = SkAnimatedImage::Make(std::move(imageDecoder->mCodec),
+ scaledSize, subset, std::move(picture));
+ if (!drawable->mDrawable) {
+ doThrowIOE(env, "Failed to create drawable");
+ return 0;
+ }
+ drawable->mDrawable->start();
+
+ return reinterpret_cast<jlong>(drawable.release());
+}
+
+static void AnimatedImageDrawable_destruct(AnimatedImageDrawable* drawable) {
+ delete drawable;
+}
+
+static jlong AnimatedImageDrawable_nGetNativeFinalizer(JNIEnv* /*env*/, jobject /*clazz*/) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&AnimatedImageDrawable_destruct));
+}
+
+static jlong AnimatedImageDrawable_nDraw(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
+ jlong canvasPtr, jlong msecs) {
+ auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+ double timeToNextUpdate = drawable->mDrawable->update(msecs);
+ auto* canvas = reinterpret_cast<Canvas*>(canvasPtr);
+ canvas->drawAnimatedImage(drawable->mDrawable.get(), 0, 0, &drawable->mPaint);
+ return (jlong) timeToNextUpdate;
+}
+
+static void AnimatedImageDrawable_nSetAlpha(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
+ jint alpha) {
+ auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+ drawable->mPaint.setAlpha(alpha);
+}
+
+static jlong AnimatedImageDrawable_nGetAlpha(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
+ auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+ return drawable->mPaint.getAlpha();
+}
+
+static void AnimatedImageDrawable_nSetColorFilter(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
+ jlong nativeFilter) {
+ auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+ auto* filter = reinterpret_cast<SkColorFilter*>(nativeFilter);
+ drawable->mPaint.setColorFilter(sk_ref_sp(filter));
+}
+
+static jboolean AnimatedImageDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
+ auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+ return drawable->mDrawable->isRunning();
+}
+
+static void AnimatedImageDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
+ auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+ drawable->mDrawable->start();
+}
+
+static void AnimatedImageDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
+ auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+ drawable->mDrawable->stop();
+}
+
+static long AnimatedImageDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
+ auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+ // FIXME: Report the size of the internal SkBitmap etc.
+ return sizeof(drawable);
+}
+
+static const JNINativeMethod gAnimatedImageDrawableMethods[] = {
+ { "nCreate", "(JLandroid/graphics/ImageDecoder;IILandroid/graphics/Rect;)J", (void*) AnimatedImageDrawable_nCreate },
+ { "nGetNativeFinalizer", "()J", (void*) AnimatedImageDrawable_nGetNativeFinalizer },
+ { "nDraw", "(JJJ)J", (void*) AnimatedImageDrawable_nDraw },
+ { "nSetAlpha", "(JI)V", (void*) AnimatedImageDrawable_nSetAlpha },
+ { "nGetAlpha", "(J)I", (void*) AnimatedImageDrawable_nGetAlpha },
+ { "nSetColorFilter", "(JJ)V", (void*) AnimatedImageDrawable_nSetColorFilter },
+ { "nIsRunning", "(J)Z", (void*) AnimatedImageDrawable_nIsRunning },
+ { "nStart", "(J)V", (void*) AnimatedImageDrawable_nStart },
+ { "nStop", "(J)V", (void*) AnimatedImageDrawable_nStop },
+ { "nNativeByteSize", "(J)J", (void*) AnimatedImageDrawable_nNativeByteSize },
+};
+
+int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env) {
+ return android::RegisterMethodsOrDie(env, "android/graphics/drawable/AnimatedImageDrawable",
+ gAnimatedImageDrawableMethods, NELEM(gAnimatedImageDrawableMethods));
+}
+
diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp
index 249202a..ed9d0e9 100644
--- a/core/jni/android/graphics/ImageDecoder.cpp
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -19,12 +19,11 @@
#include "ByteBufferStreamAdaptor.h"
#include "CreateJavaOutputStreamAdaptor.h"
#include "GraphicsJNI.h"
-#include "NinePatchPeeker.h"
+#include "ImageDecoder.h"
#include "Utils.h"
#include "core_jni_helpers.h"
#include <hwui/Bitmap.h>
-#include <hwui/Canvas.h>
#include <SkAndroidCodec.h>
#include <SkEncodedImageFormat.h>
@@ -43,34 +42,14 @@
static jclass gCorrupt_class;
static jclass gCanvas_class;
static jmethodID gImageDecoder_constructorMethodID;
+static jmethodID gImageDecoder_postProcessMethodID;
static jmethodID gPoint_constructorMethodID;
static jmethodID gIncomplete_constructorMethodID;
static jmethodID gCorrupt_constructorMethodID;
static jmethodID gCallback_onPartialImageMethodID;
-static jmethodID gPostProcess_postProcessMethodID;
static jmethodID gCanvas_constructorMethodID;
static jmethodID gCanvas_releaseMethodID;
-struct ImageDecoder {
- // These need to stay in sync with ImageDecoder.java's Allocator constants.
- enum Allocator {
- kDefault_Allocator = 0,
- kSoftware_Allocator = 1,
- kSharedMemory_Allocator = 2,
- kHardware_Allocator = 3,
- };
-
- // These need to stay in sync with PixelFormat.java's Format constants.
- enum PixelFormat {
- kUnknown = 0,
- kTranslucent = -3,
- kOpaque = -1,
- };
-
- NinePatchPeeker mPeeker;
- std::unique_ptr<SkAndroidCodec> mCodec;
-};
-
static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream) {
if (!stream.get()) {
doThrowIOE(env, "Failed to create a stream");
@@ -78,7 +57,7 @@
}
std::unique_ptr<ImageDecoder> decoder(new ImageDecoder);
SkCodec::Result result;
- auto codec = SkCodec::MakeFromStream(std::move(stream), &result, &decoder->mPeeker);
+ auto codec = SkCodec::MakeFromStream(std::move(stream), &result, decoder->mPeeker.get());
if (!codec) {
switch (result) {
case SkCodec::kIncompleteInput:
@@ -90,22 +69,24 @@
SkCodec::ResultToString(result));
doThrowIOE(env, msg.c_str());
break;
- }
+ }
return nullptr;
}
+ // FIXME: Avoid parsing the whole image?
+ const bool animated = codec->getFrameCount() > 1;
decoder->mCodec = SkAndroidCodec::MakeFromCodec(std::move(codec));
if (!decoder->mCodec.get()) {
doThrowIOE(env, "Could not create AndroidCodec");
return nullptr;
}
-
const auto& info = decoder->mCodec->getInfo();
const int width = info.width();
const int height = info.height();
return env->NewObject(gImageDecoder_class, gImageDecoder_constructorMethodID,
- reinterpret_cast<jlong>(decoder.release()), width, height);
+ reinterpret_cast<jlong>(decoder.release()), width, height,
+ animated);
}
static jobject ImageDecoder_nCreateFd(JNIEnv* env, jobject /*clazz*/,
@@ -176,6 +157,24 @@
return native_create(env, std::move(stream));
}
+jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, std::unique_ptr<Canvas> canvas,
+ int width, int height) {
+ jobject jcanvas = env->NewObject(gCanvas_class, gCanvas_constructorMethodID,
+ reinterpret_cast<jlong>(canvas.get()));
+ if (!jcanvas) {
+ doThrowOOME(env, "Failed to create Java Canvas for PostProcess!");
+ return ImageDecoder::kUnknown;
+ }
+
+ // jcanvas now owns canvas.
+ canvas.release();
+
+ return env->CallIntMethod(jimageDecoder, gImageDecoder_postProcessMethodID,
+ jcanvas, width, height);
+}
+
+// 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,
jint desiredWidth, jint desiredHeight, jobject jsubset,
@@ -322,23 +321,23 @@
// Ignore ninepatch when post-processing.
if (!jpostProcess) {
// FIXME: Share more code with BitmapFactory.cpp.
- if (decoder->mPeeker.mPatch != nullptr) {
+ if (decoder->mPeeker->mPatch != nullptr) {
if (scale) {
- decoder->mPeeker.scale(scaleX, scaleY, desiredWidth, desiredHeight);
+ decoder->mPeeker->scale(scaleX, scaleY, desiredWidth, desiredHeight);
}
- size_t ninePatchArraySize = decoder->mPeeker.mPatch->serializedSize();
+ size_t ninePatchArraySize = decoder->mPeeker->mPatch->serializedSize();
ninePatchChunk = env->NewByteArray(ninePatchArraySize);
if (ninePatchChunk == nullptr) {
doThrowOOME(env, "Failed to allocate nine patch chunk.");
return nullptr;
}
- env->SetByteArrayRegion(ninePatchChunk, 0, decoder->mPeeker.mPatchSize,
- reinterpret_cast<jbyte*>(decoder->mPeeker.mPatch));
+ env->SetByteArrayRegion(ninePatchChunk, 0, decoder->mPeeker->mPatchSize,
+ reinterpret_cast<jbyte*>(decoder->mPeeker->mPatch));
}
- if (decoder->mPeeker.mHasInsets) {
- ninePatchInsets = decoder->mPeeker.createNinePatchInsets(env, 1.0f);
+ if (decoder->mPeeker->mHasInsets) {
+ ninePatchInsets = decoder->mPeeker->createNinePatchInsets(env, 1.0f);
if (ninePatchInsets == nullptr) {
doThrowOOME(env, "Failed to allocate nine patch insets.");
return nullptr;
@@ -399,23 +398,9 @@
if (jpostProcess) {
std::unique_ptr<Canvas> canvas(Canvas::create_canvas(bm));
- jobject jcanvas = env->NewObject(gCanvas_class, gCanvas_constructorMethodID,
- reinterpret_cast<jlong>(canvas.get()));
- if (!jcanvas) {
- doThrowOOME(env, "Failed to create Java Canvas for PostProcess!");
- return nullptr;
- }
- // jcanvas will now own canvas.
- canvas.release();
- jint pixelFormat = env->CallIntMethod(jpostProcess, gPostProcess_postProcessMethodID,
- jcanvas, bm.width(), bm.height());
- if (env->ExceptionCheck()) {
- return nullptr;
- }
-
- // The Canvas objects are no longer needed, and will not remain valid.
- env->CallVoidMethod(jcanvas, gCanvas_releaseMethodID);
+ jint pixelFormat = postProcessAndRelease(env, jpostProcess, std::move(canvas),
+ bm.width(), bm.height());
if (env->ExceptionCheck()) {
return nullptr;
}
@@ -493,7 +478,7 @@
static void ImageDecoder_nGetPadding(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
jobject outPadding) {
auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
- decoder->mPeeker.getPadding(env, outPadding);
+ decoder->mPeeker->getPadding(env, outPadding);
}
static void ImageDecoder_nClose(JNIEnv* /*env*/, jobject /*clazz*/, jlong nativePtr) {
@@ -511,7 +496,7 @@
{ "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/PostProcess;IILandroid/graphics/Rect;ZIZZZ)Landroid/graphics/Bitmap;",
+ { "nDecodeBitmap", "(JLandroid/graphics/ImageDecoder$OnPartialImageListener;Landroid/graphics/ImageDecoder;IILandroid/graphics/Rect;ZIZZZ)Landroid/graphics/Bitmap;",
(void*) ImageDecoder_nDecodeBitmap },
{ "nGetSampledSize","(JI)Landroid/graphics/Point;", (void*) ImageDecoder_nGetSampledSize },
{ "nGetPadding", "(JLandroid/graphics/Rect;)V", (void*) ImageDecoder_nGetPadding },
@@ -521,7 +506,8 @@
int register_android_graphics_ImageDecoder(JNIEnv* env) {
gImageDecoder_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder"));
- gImageDecoder_constructorMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "<init>", "(JII)V");
+ gImageDecoder_constructorMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "<init>", "(JIIZ)V");
+ gImageDecoder_postProcessMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "postProcessAndRelease", "(Landroid/graphics/Canvas;II)I");
gPoint_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Point"));
gPoint_constructorMethodID = GetMethodIDOrDie(env, gPoint_class, "<init>", "(II)V");
@@ -535,9 +521,6 @@
jclass callback_class = FindClassOrDie(env, "android/graphics/ImageDecoder$OnPartialImageListener");
gCallback_onPartialImageMethodID = GetMethodIDOrDie(env, callback_class, "onPartialImage", "(Ljava/io/IOException;)Z");
- jclass postProcess_class = FindClassOrDie(env, "android/graphics/PostProcess");
- gPostProcess_postProcessMethodID = GetMethodIDOrDie(env, postProcess_class, "postProcess", "(Landroid/graphics/Canvas;II)I");
-
gCanvas_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Canvas"));
gCanvas_constructorMethodID = GetMethodIDOrDie(env, gCanvas_class, "<init>", "(J)V");
gCanvas_releaseMethodID = GetMethodIDOrDie(env, gCanvas_class, "release", "()V");
diff --git a/core/jni/android/graphics/ImageDecoder.h b/core/jni/android/graphics/ImageDecoder.h
new file mode 100644
index 0000000..2df71eb
--- /dev/null
+++ b/core/jni/android/graphics/ImageDecoder.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "NinePatchPeeker.h"
+
+#include <hwui/Canvas.h>
+
+#include <jni.h>
+
+class SkAndroidCodec;
+
+using namespace android;
+
+struct ImageDecoder {
+ // These need to stay in sync with ImageDecoder.java's Allocator constants.
+ enum Allocator {
+ kDefault_Allocator = 0,
+ kSoftware_Allocator = 1,
+ kSharedMemory_Allocator = 2,
+ kHardware_Allocator = 3,
+ };
+
+ // These need to stay in sync with PixelFormat.java's Format constants.
+ enum PixelFormat {
+ kUnknown = 0,
+ kTranslucent = -3,
+ kOpaque = -1,
+ };
+
+ std::unique_ptr<SkAndroidCodec> mCodec;
+ sk_sp<NinePatchPeeker> mPeeker;
+
+ ImageDecoder()
+ :mPeeker(new NinePatchPeeker)
+ {}
+};
+
+// 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);
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index d13e05c..05dadc9 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -26,6 +26,7 @@
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.content.res.Resources;
+import android.graphics.drawable.AnimatedImageDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.NinePatchDrawable;
@@ -294,9 +295,10 @@
};
// Fields
- private long mNativePtr;
- private final int mWidth;
- private final int mHeight;
+ private long mNativePtr;
+ private final int mWidth;
+ private final int mHeight;
+ private final boolean mAnimated;
private int mDesiredWidth;
private int mDesiredHeight;
@@ -322,12 +324,14 @@
* called after decoding to delete native resources.
*/
@SuppressWarnings("unused")
- private ImageDecoder(long nativePtr, int width, int height) {
+ private ImageDecoder(long nativePtr, int width, int height,
+ boolean animated) {
mNativePtr = nativePtr;
mWidth = width;
mHeight = height;
mDesiredWidth = width;
mDesiredHeight = height;
+ mAnimated = animated;
mCloseGuard.open("close");
}
@@ -677,6 +681,18 @@
}
}
+ private Bitmap decodeBitmap() throws IOException {
+ checkState();
+ // nDecodeBitmap calls postProcessAndRelease only if mPostProcess
+ // exists.
+ ImageDecoder postProcessPtr = mPostProcess == null ? null : this;
+ return nDecodeBitmap(mNativePtr, mOnPartialImageListener,
+ postProcessPtr, mDesiredWidth, mDesiredHeight, mCropRect,
+ mMutable, mAllocator, mRequireUnpremultiplied,
+ mPreferRamOverQuality, mAsAlphaMask);
+
+ }
+
/**
* Create a {@link Drawable} from a {@code Source}.
*
@@ -702,8 +718,6 @@
}
}
- decoder.checkState();
-
if (decoder.mRequireUnpremultiplied) {
// Though this could be supported (ignored) for opaque images,
// it seems better to always report this error.
@@ -716,17 +730,22 @@
"Drawable!");
}
- Bitmap bm = nDecodeBitmap(decoder.mNativePtr,
- decoder.mOnPartialImageListener,
- decoder.mPostProcess,
- decoder.mDesiredWidth,
- decoder.mDesiredHeight,
- decoder.mCropRect,
- false, // mMutable
- decoder.mAllocator,
- false, // mRequireUnpremultiplied
- decoder.mPreferRamOverQuality,
- decoder.mAsAlphaMask);
+ if (decoder.mAnimated) {
+ // AnimatedImageDrawable calls postProcessAndRelease only if
+ // mPostProcess exists.
+ ImageDecoder postProcessPtr = decoder.mPostProcess == null ?
+ null : decoder;
+ Drawable d = new AnimatedImageDrawable(decoder.mNativePtr,
+ postProcessPtr, decoder.mDesiredWidth,
+ decoder.mDesiredHeight, decoder.mCropRect,
+ decoder.mInputStream, decoder.mAssetFd);
+ // d has taken ownership of these objects.
+ decoder.mInputStream = null;
+ decoder.mAssetFd = null;
+ return d;
+ }
+
+ Bitmap bm = decoder.decodeBitmap();
Resources res = src.getResources();
if (res == null) {
bm.setDensity(Bitmap.DENSITY_NONE);
@@ -742,7 +761,6 @@
opticalInsets, null);
}
- // TODO: Handle animation.
return new BitmapDrawable(res, bm);
}
}
@@ -781,19 +799,7 @@
}
}
- decoder.checkState();
-
- return nDecodeBitmap(decoder.mNativePtr,
- decoder.mOnPartialImageListener,
- decoder.mPostProcess,
- decoder.mDesiredWidth,
- decoder.mDesiredHeight,
- decoder.mCropRect,
- decoder.mMutable,
- decoder.mAllocator,
- decoder.mRequireUnpremultiplied,
- decoder.mPreferRamOverQuality,
- decoder.mAsAlphaMask);
+ return decoder.decodeBitmap();
}
}
@@ -809,6 +815,18 @@
return decodeBitmap(src, null);
}
+ /**
+ * Private method called by JNI.
+ */
+ @SuppressWarnings("unused")
+ private int postProcessAndRelease(@NonNull Canvas canvas, int width, int height) {
+ try {
+ return mPostProcess.postProcess(canvas, width, height);
+ } finally {
+ canvas.release();
+ }
+ }
+
private static native ImageDecoder nCreate(long asset) throws IOException;
private static native ImageDecoder nCreate(ByteBuffer buffer,
int position,
@@ -820,7 +838,7 @@
@NonNull
private static native Bitmap nDecodeBitmap(long nativePtr,
OnPartialImageListener listener,
- PostProcess postProcess,
+ @Nullable ImageDecoder decoder, // Only used if mPostProcess != null
int width, int height,
Rect cropRect, boolean mutable,
int allocator, boolean requireUnpremul,
diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
new file mode 100644
index 0000000..ce3bd9a
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.drawable;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.AssetFileDescriptor;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.ImageDecoder;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.SystemClock;
+
+import libcore.io.IoUtils;
+import libcore.util.NativeAllocationRegistry;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.Runnable;
+
+/**
+ * @hide
+ */
+public class AnimatedImageDrawable extends Drawable implements Animatable {
+ private final long mNativePtr;
+ private final InputStream mInputStream;
+ private final AssetFileDescriptor mAssetFd;
+
+ private final int mIntrinsicWidth;
+ private final int mIntrinsicHeight;
+
+ private Runnable mRunnable = new Runnable() {
+ @Override
+ public void run() {
+ invalidateSelf();
+ }
+ };
+
+ /**
+ * @hide
+ * This should only be called by ImageDecoder.
+ *
+ * decoder is only non-null if it has a PostProcess
+ */
+ public AnimatedImageDrawable(long nativeImageDecoder,
+ @Nullable ImageDecoder decoder, int width, int height, Rect cropRect,
+ InputStream inputStream, AssetFileDescriptor afd)
+ throws IOException {
+ mNativePtr = nCreate(nativeImageDecoder, decoder, width, height, cropRect);
+ mInputStream = inputStream;
+ mAssetFd = afd;
+
+ if (cropRect == null) {
+ mIntrinsicWidth = width;
+ mIntrinsicHeight = height;
+ } else {
+ mIntrinsicWidth = cropRect.width();
+ mIntrinsicHeight = cropRect.height();
+ }
+
+ long nativeSize = nNativeByteSize(mNativePtr);
+ NativeAllocationRegistry registry = new NativeAllocationRegistry(
+ AnimatedImageDrawable.class.getClassLoader(), nGetNativeFinalizer(), nativeSize);
+ registry.registerNativeAllocation(this, mNativePtr);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ // FIXME: It's a shame that we have *both* a native finalizer and a Java
+ // one. The native one is necessary to report how much memory is being
+ // used natively, and this one is necessary to close the input. An
+ // alternative might be to read the entire stream ahead of time, so we
+ // can eliminate the Java finalizer.
+ try {
+ IoUtils.closeQuietly(mInputStream);
+ IoUtils.closeQuietly(mAssetFd);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mIntrinsicWidth;
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mIntrinsicHeight;
+ }
+
+ @Override
+ public void draw(@NonNull Canvas canvas) {
+ long nextUpdate = nDraw(mNativePtr, canvas.getNativeCanvasWrapper(),
+ SystemClock.uptimeMillis());
+ scheduleSelf(mRunnable, nextUpdate);
+ }
+
+ @Override
+ public void setAlpha(@IntRange(from=0,to=255) int alpha) {
+ if (alpha < 0 || alpha > 255) {
+ throw new IllegalArgumentException("Alpha must be between 0 and"
+ + " 255! provided " + alpha);
+ }
+ nSetAlpha(mNativePtr, alpha);
+ }
+
+ @Override
+ public int getAlpha() {
+ return nGetAlpha(mNativePtr);
+ }
+
+ @Override
+ public void setColorFilter(@Nullable ColorFilter colorFilter) {
+ long nativeFilter = colorFilter == null ? 0 : colorFilter.getNativeInstance();
+ nSetColorFilter(mNativePtr, nativeFilter);
+ }
+
+ @Override
+ public @PixelFormat.Opacity int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+
+ // TODO: Add a Constant State?
+ // @Override
+ // public @Nullable ConstantState getConstantState() {}
+
+
+ // Animatable overrides
+ @Override
+ public boolean isRunning() {
+ return nIsRunning(mNativePtr);
+ }
+
+ @Override
+ public void start() {
+ nStart(mNativePtr);
+ }
+
+ @Override
+ public void stop() {
+ nStop(mNativePtr);
+ }
+
+ private static native long nCreate(long nativeImageDecoder,
+ @Nullable ImageDecoder decoder, int width, int height, Rect cropRect)
+ throws IOException;
+ private static native long nGetNativeFinalizer();
+ private static native long nDraw(long nativePtr, long canvasNativePtr, long msecs);
+ private static native void nSetAlpha(long nativePtr, int alpha);
+ private static native int nGetAlpha(long nativePtr);
+ private static native void nSetColorFilter(long nativePtr, long nativeFilter);
+ private static native boolean nIsRunning(long nativePtr);
+ private static native void nStart(long nativePtr);
+ private static native void nStop(long nativePtr);
+ private static native long nNativeByteSize(long nativePtr);
+}
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 3fb1c0d..fb7b246 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -495,6 +495,11 @@
refPaint(paint), refBitmap(bitmap), refPatch(&patch)));
}
+void RecordingCanvas::drawAnimatedImage(SkAnimatedImage*, float left, float top,
+ const SkPaint*) {
+ // Unimplemented
+}
+
// Text
void RecordingCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int glyphCount, const SkPaint& paint,
float x, float y, float boundsLeft, float boundsTop,
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 3087db0..dd06ada 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -183,6 +183,8 @@
virtual void drawNinePatch(Bitmap& bitmap, const android::Res_png_9patch& chunk, float dstLeft,
float dstTop, float dstRight, float dstBottom,
const SkPaint* paint) override;
+ virtual void drawAnimatedImage(SkAnimatedImage*, float left, float top,
+ const SkPaint* paint) override;
// Text
virtual bool drawTextAbsolutePos() const override { return false; }
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 2e08670..dc274cf 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -23,6 +23,7 @@
#include "hwui/MinikinUtils.h"
#include "pipeline/skia/AnimatedDrawables.h"
+#include <SkAnimatedImage.h>
#include <SkCanvasStateUtils.h>
#include <SkColorFilter.h>
#include <SkColorSpaceXformCanvas.h>
@@ -32,6 +33,7 @@
#include <SkGraphics.h>
#include <SkImage.h>
#include <SkImagePriv.h>
+#include <SkPicture.h>
#include <SkRSXform.h>
#include <SkShader.h>
#include <SkTemplates.h>
@@ -723,6 +725,20 @@
mCanvas->drawImageLattice(image.get(), lattice, dst, addFilter(paint, &tmpPaint, colorFilter));
}
+void SkiaCanvas::drawAnimatedImage(SkAnimatedImage* image, float left, float top,
+ const SkPaint* paint) {
+ sk_sp<SkPicture> pic(image->newPictureSnapshot());
+ SkMatrix matrixStorage;
+ SkMatrix* matrix;
+ if (left == 0.0f && top == 0.0f) {
+ matrix = nullptr;
+ } else {
+ matrixStorage = SkMatrix::MakeTrans(left, top);
+ matrix = &matrixStorage;
+ }
+ mCanvas->drawPicture(pic.get(), matrix, paint);
+}
+
void SkiaCanvas::drawVectorDrawable(VectorDrawableRoot* vectorDrawable) {
vectorDrawable->drawStaging(this);
}
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 99e676a..7137210 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -124,6 +124,8 @@
virtual void drawNinePatch(Bitmap& bitmap, const android::Res_png_9patch& chunk, float dstLeft,
float dstTop, float dstRight, float dstBottom,
const SkPaint* paint) override;
+ virtual void drawAnimatedImage(SkAnimatedImage*, float left, float top,
+ const SkPaint* paint) override;
virtual bool drawTextAbsolutePos() const override { return true; }
virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index e682a2e..5efd357 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -28,6 +28,7 @@
#include <SkCanvas.h>
#include <SkMatrix.h>
+class SkAnimatedImage;
class SkCanvasState;
class SkVertices;
@@ -237,6 +238,9 @@
float dstTop, float dstRight, float dstBottom,
const SkPaint* paint) = 0;
+ virtual void drawAnimatedImage(SkAnimatedImage*, float left, float top,
+ const SkPaint* paint) = 0;
+
/**
* Specifies if the positions passed to ::drawText are absolute or relative
* to the (x,y) value provided.