1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "GraphicsJNI.h" 18 #include "ImageDecoder.h" 19 #include "Utils.h" 20 #include "core_jni_helpers.h" 21 22 #include <SkAndroidCodec.h> 23 #include <SkAnimatedImage.h> 24 #include <SkColorFilter.h> 25 #include <SkPicture.h> 26 #include <SkPictureRecorder.h> 27 #include <hwui/AnimatedImageDrawable.h> 28 #include <hwui/Canvas.h> 29 #include <utils/Looper.h> 30 31 using namespace android; 32 33 static jmethodID gAnimatedImageDrawable_onAnimationEndMethodID; 34 35 // Note: jpostProcess holds a handle to the ImageDecoder. 36 static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/, 37 jlong nativeImageDecoder, jobject jpostProcess, 38 jint width, jint height, jobject jsubset) { 39 if (nativeImageDecoder == 0) { 40 doThrowIOE(env, "Cannot create AnimatedImageDrawable from null!"); 41 return 0; 42 } 43 44 auto* imageDecoder = reinterpret_cast<ImageDecoder*>(nativeImageDecoder); 45 const SkISize scaledSize = SkISize::Make(width, height); 46 SkIRect subset; 47 if (jsubset) { 48 GraphicsJNI::jrect_to_irect(env, jsubset, &subset); 49 } else { 50 subset = SkIRect::MakeWH(width, height); 51 } 52 53 auto info = imageDecoder->mCodec->getInfo(); 54 bool hasRestoreFrame = false; 55 if (imageDecoder->mCodec->getEncodedFormat() == SkEncodedImageFormat::kWEBP) { 56 if (width < info.width() && height < info.height()) { 57 // WebP will scale its SkBitmap to the scaled size. 58 // FIXME: b/73529447 GIF should do the same. 59 info = info.makeWH(width, height); 60 } 61 } else { 62 const int frameCount = imageDecoder->mCodec->codec()->getFrameCount(); 63 for (int i = 0; i < frameCount; ++i) { 64 SkCodec::FrameInfo frameInfo; 65 if (!imageDecoder->mCodec->codec()->getFrameInfo(i, &frameInfo)) { 66 doThrowIOE(env, "Failed to read frame info!"); 67 return 0; 68 } 69 if (frameInfo.fDisposalMethod == SkCodecAnimation::DisposalMethod::kRestorePrevious) { 70 hasRestoreFrame = true; 71 break; 72 } 73 } 74 } 75 76 size_t bytesUsed = info.computeMinByteSize(); 77 // SkAnimatedImage has one SkBitmap for decoding, plus an extra one if there is a 78 // kRestorePrevious frame. AnimatedImageDrawable has two SkPictures storing the current 79 // frame and the next frame. (The former assumes that the image is animated, and the 80 // latter assumes that it is drawn to a hardware canvas.) 81 bytesUsed *= hasRestoreFrame ? 4 : 3; 82 sk_sp<SkPicture> picture; 83 if (jpostProcess) { 84 SkRect bounds = SkRect::MakeWH(subset.width(), subset.height()); 85 86 SkPictureRecorder recorder; 87 SkCanvas* skcanvas = recorder.beginRecording(bounds); 88 std::unique_ptr<Canvas> canvas(Canvas::create_canvas(skcanvas)); 89 postProcessAndRelease(env, jpostProcess, std::move(canvas)); 90 if (env->ExceptionCheck()) { 91 return 0; 92 } 93 picture = recorder.finishRecordingAsPicture(); 94 bytesUsed += picture->approximateBytesUsed(); 95 } 96 97 98 sk_sp<SkAnimatedImage> animatedImg = SkAnimatedImage::Make(std::move(imageDecoder->mCodec), 99 scaledSize, subset, 100 std::move(picture)); 101 if (!animatedImg) { 102 doThrowIOE(env, "Failed to create drawable"); 103 return 0; 104 } 105 106 bytesUsed += sizeof(animatedImg.get()); 107 108 sk_sp<AnimatedImageDrawable> drawable(new AnimatedImageDrawable(std::move(animatedImg), 109 bytesUsed)); 110 return reinterpret_cast<jlong>(drawable.release()); 111 } 112 113 static void AnimatedImageDrawable_destruct(AnimatedImageDrawable* drawable) { 114 SkSafeUnref(drawable); 115 } 116 117 static jlong AnimatedImageDrawable_nGetNativeFinalizer(JNIEnv* /*env*/, jobject /*clazz*/) { 118 return static_cast<jlong>(reinterpret_cast<uintptr_t>(&AnimatedImageDrawable_destruct)); 119 } 120 121 // Java's FINISHED relies on this being -1 122 static_assert(SkAnimatedImage::kFinished == -1); 123 124 static jlong AnimatedImageDrawable_nDraw(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, 125 jlong canvasPtr) { 126 auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); 127 auto* canvas = reinterpret_cast<Canvas*>(canvasPtr); 128 return (jlong) canvas->drawAnimatedImage(drawable); 129 } 130 131 static void AnimatedImageDrawable_nSetAlpha(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, 132 jint alpha) { 133 auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); 134 drawable->setStagingAlpha(alpha); 135 } 136 137 static jlong AnimatedImageDrawable_nGetAlpha(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { 138 auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); 139 return drawable->getStagingAlpha(); 140 } 141 142 static void AnimatedImageDrawable_nSetColorFilter(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, 143 jlong nativeFilter) { 144 auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); 145 auto* filter = reinterpret_cast<SkColorFilter*>(nativeFilter); 146 drawable->setStagingColorFilter(sk_ref_sp(filter)); 147 } 148 149 static jboolean AnimatedImageDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { 150 auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); 151 return drawable->isRunning(); 152 } 153 154 static jboolean AnimatedImageDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { 155 auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); 156 return drawable->start(); 157 } 158 159 static jboolean AnimatedImageDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { 160 auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); 161 return drawable->stop(); 162 } 163 164 // Java's LOOP_INFINITE relies on this being the same. 165 static_assert(SkCodec::kRepetitionCountInfinite == -1); 166 167 static jint AnimatedImageDrawable_nGetRepeatCount(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { 168 auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); 169 return drawable->getRepetitionCount(); 170 } 171 172 static void AnimatedImageDrawable_nSetRepeatCount(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, 173 jint loopCount) { 174 auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); 175 drawable->setRepetitionCount(loopCount); 176 } 177 178 class InvokeListener : public MessageHandler { 179 public: 180 InvokeListener(JNIEnv* env, jobject javaObject) { 181 LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&mJvm) != JNI_OK); 182 // Hold a weak reference to break a cycle that would prevent GC. 183 mWeakRef = env->NewWeakGlobalRef(javaObject); 184 } 185 186 ~InvokeListener() override { 187 auto* env = get_env_or_die(mJvm); 188 env->DeleteWeakGlobalRef(mWeakRef); 189 } 190 191 virtual void handleMessage(const Message&) override { 192 auto* env = get_env_or_die(mJvm); 193 jobject localRef = env->NewLocalRef(mWeakRef); 194 if (localRef) { 195 env->CallVoidMethod(localRef, gAnimatedImageDrawable_onAnimationEndMethodID); 196 } 197 } 198 199 private: 200 JavaVM* mJvm; 201 jweak mWeakRef; 202 }; 203 204 class JniAnimationEndListener : public OnAnimationEndListener { 205 public: 206 JniAnimationEndListener(sp<Looper>&& looper, JNIEnv* env, jobject javaObject) { 207 mListener = new InvokeListener(env, javaObject); 208 mLooper = std::move(looper); 209 } 210 211 void onAnimationEnd() override { mLooper->sendMessage(mListener, 0); } 212 213 private: 214 sp<InvokeListener> mListener; 215 sp<Looper> mLooper; 216 }; 217 218 static void AnimatedImageDrawable_nSetOnAnimationEndListener(JNIEnv* env, jobject /*clazz*/, 219 jlong nativePtr, jobject jdrawable) { 220 auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); 221 if (!jdrawable) { 222 drawable->setOnAnimationEndListener(nullptr); 223 } else { 224 sp<Looper> looper = Looper::getForThread(); 225 if (!looper.get()) { 226 doThrowISE(env, 227 "Must set AnimatedImageDrawable's AnimationCallback on a thread with a " 228 "looper!"); 229 return; 230 } 231 232 drawable->setOnAnimationEndListener( 233 std::make_unique<JniAnimationEndListener>(std::move(looper), env, jdrawable)); 234 } 235 } 236 237 static jlong AnimatedImageDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { 238 auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); 239 return drawable->byteSize(); 240 } 241 242 static void AnimatedImageDrawable_nSetMirrored(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, 243 jboolean mirrored) { 244 auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); 245 drawable->setStagingMirrored(mirrored); 246 } 247 248 static const JNINativeMethod gAnimatedImageDrawableMethods[] = { 249 { "nCreate", "(JLandroid/graphics/ImageDecoder;IILandroid/graphics/Rect;)J", (void*) AnimatedImageDrawable_nCreate }, 250 { "nGetNativeFinalizer", "()J", (void*) AnimatedImageDrawable_nGetNativeFinalizer }, 251 { "nDraw", "(JJ)J", (void*) AnimatedImageDrawable_nDraw }, 252 { "nSetAlpha", "(JI)V", (void*) AnimatedImageDrawable_nSetAlpha }, 253 { "nGetAlpha", "(J)I", (void*) AnimatedImageDrawable_nGetAlpha }, 254 { "nSetColorFilter", "(JJ)V", (void*) AnimatedImageDrawable_nSetColorFilter }, 255 { "nIsRunning", "(J)Z", (void*) AnimatedImageDrawable_nIsRunning }, 256 { "nStart", "(J)Z", (void*) AnimatedImageDrawable_nStart }, 257 { "nStop", "(J)Z", (void*) AnimatedImageDrawable_nStop }, 258 { "nGetRepeatCount", "(J)I", (void*) AnimatedImageDrawable_nGetRepeatCount }, 259 { "nSetRepeatCount", "(JI)V", (void*) AnimatedImageDrawable_nSetRepeatCount }, 260 { "nSetOnAnimationEndListener", "(JLandroid/graphics/drawable/AnimatedImageDrawable;)V", (void*) AnimatedImageDrawable_nSetOnAnimationEndListener }, 261 { "nNativeByteSize", "(J)J", (void*) AnimatedImageDrawable_nNativeByteSize }, 262 { "nSetMirrored", "(JZ)V", (void*) AnimatedImageDrawable_nSetMirrored }, 263 }; 264 265 int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env) { 266 jclass animatedImageDrawable_class = FindClassOrDie(env, "android/graphics/drawable/AnimatedImageDrawable"); 267 gAnimatedImageDrawable_onAnimationEndMethodID = GetMethodIDOrDie(env, animatedImageDrawable_class, "onAnimationEnd", "()V"); 268 269 return android::RegisterMethodsOrDie(env, "android/graphics/drawable/AnimatedImageDrawable", 270 gAnimatedImageDrawableMethods, NELEM(gAnimatedImageDrawableMethods)); 271 } 272 273