From 671cce2605ed50c9aba73ab5bd530cb7741c53cd Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Sun, 14 Jan 2018 16:52:17 -0500 Subject: [PATCH] Make ImageDecoder return animated Drawables Bug: 63909536 Bug: 63908092 Test: TODO If ImageDecoder.decodeDrawable is called with an animated image Source (currently GIF or WebP), return an object of a new (hidden) Drawable subclass. The new Drawable animates, and it implements Animatable (TODO: implement Animatable2) so users have some control over the animation. In addition to the normal features of Drawable, this new one supports many of the features of ImageDecoder, including scaling, cropping and PostProcess, which enables circle masks/rounded corners and other arbitrary after-effects. It does *not* support decoding directly to a Hardware Bitmap, since it cycles through frames and reuses the same bitmap memory. But it could be made to use shared memory (TODO?). TODO: Use a better number for the native allocation registry TODO: Use the RenderThread to drive the animation, and remove decoding on the UI thread. TODO: Add support for modifying the loop count Android.bp: - build new AnimatedImageDrawable.cpp AndroidRuntime.cpp: - register new native methods AnimatedImageDrawable.java AnimatedImageDrawable.cpp: - new Drawable that handles animated images Canvas.h, SkiaCanvas.h/.cpp - New virtual method and implementation for drawing SkAnimatedImages RecordingCanvas.h/.cpp - Stub implementation of drawing SkAnimatedImages ImageDecoder.h/cpp - Allow code sharing with AnimatedImageDrawable.cpp - postProcess - access the ImageDecoder struct Depends on https://skia-review.googlesource.com/c/skia/+/94660 in Skia. Change-Id: Ie2ec98d9c52deda4d439c6ef8e5dea2861bb93a3 --- core/jni/Android.bp | 1 + core/jni/AndroidRuntime.cpp | 2 + .../graphics/AnimatedImageDrawable.cpp | 154 ++++++++++++++++ core/jni/android/graphics/ImageDecoder.cpp | 55 ++---- core/jni/android/graphics/ImageDecoder.h | 55 ++++++ .../java/android/graphics/ImageDecoder.java | 28 ++- .../drawable/AnimatedImageDrawable.java | 173 ++++++++++++++++++ libs/hwui/RecordingCanvas.cpp | 5 + libs/hwui/RecordingCanvas.h | 2 + libs/hwui/SkiaCanvas.cpp | 16 ++ libs/hwui/SkiaCanvas.h | 2 + libs/hwui/hwui/Canvas.h | 4 + 12 files changed, 455 insertions(+), 42 deletions(-) create mode 100644 core/jni/android/graphics/AnimatedImageDrawable.cpp create mode 100644 core/jni/android/graphics/ImageDecoder.h create mode 100644 graphics/java/android/graphics/drawable/AnimatedImageDrawable.java diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 96f3308ec178d..53913f198c1d1 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -120,6 +120,7 @@ cc_library_shared { "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 6569b4783e673..ef0e260f2ee34 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -63,6 +63,7 @@ extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env); 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); @@ -1397,6 +1398,7 @@ static const RegJNIRec gRegJNI[] = { 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 0000000000000..12feaab5c6846 --- /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 +#include +#include +#include +#include +#include + +using namespace android; + +struct AnimatedImageDrawable { + sk_sp 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(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 picture; + if (jpostProcess) { + SkRect bounds = SkRect::MakeWH(subset.width(), subset.height()); + + SkPictureRecorder recorder; + SkCanvas* skcanvas = recorder.beginRecording(bounds); + std::unique_ptr 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 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(drawable.release()); +} + +static void AnimatedImageDrawable_destruct(AnimatedImageDrawable* drawable) { + delete drawable; +} + +static jlong AnimatedImageDrawable_nGetNativeFinalizer(JNIEnv* /*env*/, jobject /*clazz*/) { + return static_cast(reinterpret_cast(&AnimatedImageDrawable_destruct)); +} + +static jlong AnimatedImageDrawable_nDraw(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, + jlong canvasPtr, jlong msecs) { + auto* drawable = reinterpret_cast(nativePtr); + double timeToNextUpdate = drawable->mDrawable->update(msecs); + auto* canvas = reinterpret_cast(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(nativePtr); + drawable->mPaint.setAlpha(alpha); +} + +static jlong AnimatedImageDrawable_nGetAlpha(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast(nativePtr); + return drawable->mPaint.getAlpha(); +} + +static void AnimatedImageDrawable_nSetColorFilter(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, + jlong nativeFilter) { + auto* drawable = reinterpret_cast(nativePtr); + auto* filter = reinterpret_cast(nativeFilter); + drawable->mPaint.setColorFilter(sk_ref_sp(filter)); +} + +static jboolean AnimatedImageDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast(nativePtr); + return drawable->mDrawable->isRunning(); +} + +static void AnimatedImageDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast(nativePtr); + drawable->mDrawable->start(); +} + +static void AnimatedImageDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast(nativePtr); + drawable->mDrawable->stop(); +} + +static long AnimatedImageDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast(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 9e6803784d953..ed9d0e9e86c31 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 -#include #include #include @@ -51,26 +50,6 @@ static jmethodID gCallback_onPartialImageMethodID; 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 mCodec; -}; - static jobject native_create(JNIEnv* env, std::unique_ptr stream) { if (!stream.get()) { doThrowIOE(env, "Failed to create a stream"); @@ -78,7 +57,7 @@ static jobject native_create(JNIEnv* env, std::unique_ptr stream) { } std::unique_ptr 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 @@ static jobject native_create(JNIEnv* env, std::unique_ptr stream) { 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(decoder.release()), width, height); + reinterpret_cast(decoder.release()), width, height, + animated); } static jobject ImageDecoder_nCreateFd(JNIEnv* env, jobject /*clazz*/, @@ -176,8 +157,8 @@ static jobject ImageDecoder_nCreateByteArray(JNIEnv* env, jobject /*clazz*/, jby return native_create(env, std::move(stream)); } -static jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, - std::unique_ptr canvas, int width, int height) { +jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, std::unique_ptr canvas, + int width, int height) { jobject jcanvas = env->NewObject(gCanvas_class, gCanvas_constructorMethodID, reinterpret_cast(canvas.get())); if (!jcanvas) { @@ -340,23 +321,23 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong // 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(decoder->mPeeker.mPatch)); + env->SetByteArrayRegion(ninePatchChunk, 0, decoder->mPeeker->mPatchSize, + reinterpret_cast(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; @@ -497,7 +478,7 @@ static jobject ImageDecoder_nGetSampledSize(JNIEnv* env, jobject /*clazz*/, jlon static void ImageDecoder_nGetPadding(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, jobject outPadding) { auto* decoder = reinterpret_cast(nativePtr); - decoder->mPeeker.getPadding(env, outPadding); + decoder->mPeeker->getPadding(env, outPadding); } static void ImageDecoder_nClose(JNIEnv* /*env*/, jobject /*clazz*/, jlong nativePtr) { @@ -525,7 +506,7 @@ static const JNINativeMethod gImageDecoderMethods[] = { int register_android_graphics_ImageDecoder(JNIEnv* env) { gImageDecoder_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder")); - gImageDecoder_constructorMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "", "(JII)V"); + gImageDecoder_constructorMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "", "(JIIZ)V"); gImageDecoder_postProcessMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "postProcessAndRelease", "(Landroid/graphics/Canvas;II)I"); gPoint_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Point")); diff --git a/core/jni/android/graphics/ImageDecoder.h b/core/jni/android/graphics/ImageDecoder.h new file mode 100644 index 0000000000000..2df71eb19528d --- /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 + +#include + +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 mCodec; + sk_sp 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, + int width, int height); diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index f8ca29e48d855..05dadc97a5bab 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -26,6 +26,7 @@ import android.content.ContentResolver; 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 @@ public final class ImageDecoder implements AutoCloseable { }; // 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 @@ public final class ImageDecoder implements AutoCloseable { * 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"); } @@ -726,6 +730,21 @@ public final class ImageDecoder implements AutoCloseable { "Drawable!"); } + 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) { @@ -742,7 +761,6 @@ public final class ImageDecoder implements AutoCloseable { opticalInsets, null); } - // TODO: Handle animation. return new BitmapDrawable(res, bm); } } diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java new file mode 100644 index 0000000000000..ce3bd9af73b65 --- /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 3fb1c0d64abf6..fb7b24623568b 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -495,6 +495,11 @@ void RecordingCanvas::drawNinePatch(Bitmap& bitmap, const android::Res_png_9patc 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 3087db0550dec..dd06ada9da3d7 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -183,6 +183,8 @@ public: 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 2e08670a757a6..dc274cf50a52f 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 #include #include #include @@ -32,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -723,6 +725,20 @@ void SkiaCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& chunk, floa mCanvas->drawImageLattice(image.get(), lattice, dst, addFilter(paint, &tmpPaint, colorFilter)); } +void SkiaCanvas::drawAnimatedImage(SkAnimatedImage* image, float left, float top, + const SkPaint* paint) { + sk_sp 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 99e676a6fb1e1..7137210406fb8 100644 --- a/libs/hwui/SkiaCanvas.h +++ b/libs/hwui/SkiaCanvas.h @@ -124,6 +124,8 @@ public: 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 e682a2e226b79..5efd35764635f 100644 --- a/libs/hwui/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -28,6 +28,7 @@ #include #include +class SkAnimatedImage; class SkCanvasState; class SkVertices; @@ -237,6 +238,9 @@ public: 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.