Merge changes Ie2ec98d9,I81ce2bef
* changes: Make ImageDecoder return animated Drawables Ensure that PostProcess Canvas is released
This commit is contained in:
committed by
Android (Google) Code Review
commit
4ef6684925
@@ -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",
|
||||
|
||||
@@ -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),
|
||||
|
||||
154
core/jni/android/graphics/AnimatedImageDrawable.cpp
Normal file
154
core/jni/android/graphics/AnimatedImageDrawable.cpp
Normal file
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
55
core/jni/android/graphics/ImageDecoder.h
Normal file
55
core/jni/android/graphics/ImageDecoder.h
Normal file
@@ -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);
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user