diff options
| -rw-r--r-- | core/jni/Android.bp | 1 | ||||
| -rw-r--r-- | core/jni/AndroidRuntime.cpp | 2 | ||||
| -rw-r--r-- | core/jni/android/graphics/AnimatedImageDrawable.cpp | 154 | ||||
| -rw-r--r-- | core/jni/android/graphics/ImageDecoder.cpp | 95 | ||||
| -rw-r--r-- | core/jni/android/graphics/ImageDecoder.h | 55 | ||||
| -rw-r--r-- | graphics/java/android/graphics/ImageDecoder.java | 82 | ||||
| -rw-r--r-- | graphics/java/android/graphics/drawable/AnimatedImageDrawable.java | 173 | ||||
| -rw-r--r-- | libs/hwui/RecordingCanvas.cpp | 5 | ||||
| -rw-r--r-- | libs/hwui/RecordingCanvas.h | 2 | ||||
| -rw-r--r-- | libs/hwui/SkiaCanvas.cpp | 16 | ||||
| -rw-r--r-- | libs/hwui/SkiaCanvas.h | 2 | ||||
| -rw-r--r-- | libs/hwui/hwui/Canvas.h | 4 |
12 files changed, 503 insertions, 88 deletions
diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 77d0617cd9ac..543acc7c2158 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -119,6 +119,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 068197a4ed56..aa9a82415f97 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); @@ -1396,6 +1397,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 000000000000..12feaab5c684 --- /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 249202a117e1..ed9d0e9e86c3 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 gIncomplete_class; 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 @@ static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream) { } 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 @@ static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> 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<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 @@ static jobject ImageDecoder_nCreateByteArray(JNIEnv* env, jobject /*clazz*/, jby 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 @@ 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<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 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong 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 jobject ImageDecoder_nGetSampledSize(JNIEnv* env, jobject /*clazz*/, jlon 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 @@ static const JNINativeMethod gImageDecoderMethods[] = { { "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 @@ 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, "<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 @@ int register_android_graphics_ImageDecoder(JNIEnv* env) { 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 000000000000..2df71eb19528 --- /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 d13e05c22509..05dadc97a5ba 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"); } @@ -677,6 +681,18 @@ public final class ImageDecoder implements AutoCloseable { } } + 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 @@ public final class ImageDecoder implements AutoCloseable { } } - 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 @@ public final class ImageDecoder implements AutoCloseable { "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 @@ public final class ImageDecoder implements AutoCloseable { opticalInsets, null); } - // TODO: Handle animation. return new BitmapDrawable(res, bm); } } @@ -781,19 +799,7 @@ public final class ImageDecoder implements AutoCloseable { } } - 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 @@ public final class ImageDecoder implements AutoCloseable { 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 @@ public final class ImageDecoder implements AutoCloseable { @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 000000000000..ce3bd9af73b6 --- /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 3fb1c0d64abf..fb7b24623568 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 3087db0550de..dd06ada9da3d 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 2e08670a757a..dc274cf50a52 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 @@ 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<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 99e676a6fb1e..7137210406fb 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 e682a2e226b7..5efd35764635 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 @@ 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. |