blob: d3bb70f60d67cddd14fee95e7c37b0b910c70784 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/shell/platform/android/android_image_generator.h"
#include <memory>
#include <android/bitmap.h>
#include <android/hardware_buffer.h>
#include "flutter/fml/platform/android/jni_util.h"
namespace flutter {
static fml::jni::ScopedJavaGlobalRef<jclass>* g_flutter_jni_class = nullptr;
static jmethodID g_decode_image_method = nullptr;
AndroidImageGenerator::~AndroidImageGenerator() = default;
AndroidImageGenerator::AndroidImageGenerator(sk_sp<SkData> data)
: data_(data), image_info_(SkImageInfo::MakeUnknown(-1, -1)) {}
const SkImageInfo& AndroidImageGenerator::GetInfo() {
header_decoded_latch_.Wait();
return image_info_;
}
unsigned int AndroidImageGenerator::GetFrameCount() const {
return 1;
}
unsigned int AndroidImageGenerator::GetPlayCount() const {
return 1;
}
const ImageGenerator::FrameInfo AndroidImageGenerator::GetFrameInfo(
unsigned int frame_index) const {
return {.required_frame = std::nullopt,
.duration = 0,
.disposal_method = SkCodecAnimation::DisposalMethod::kKeep};
}
SkISize AndroidImageGenerator::GetScaledDimensions(float desired_scale) {
return GetInfo().dimensions();
}
bool AndroidImageGenerator::GetPixels(const SkImageInfo& info,
void* pixels,
size_t row_bytes,
unsigned int frame_index,
std::optional<unsigned int> prior_frame) {
fully_decoded_latch_.Wait();
if (!software_decoded_data_) {
return false;
}
if (kRGBA_8888_SkColorType != info.colorType()) {
return false;
}
switch (info.alphaType()) {
case kOpaque_SkAlphaType:
if (kOpaque_SkAlphaType != GetInfo().alphaType()) {
return false;
}
break;
case kPremul_SkAlphaType:
break;
default:
return false;
}
// TODO(bdero): Override `GetImage()` to use `SkImage::FromAHardwareBuffer` on
// API level 30+ once it's updated to do symbol lookups and not get
// preprocessed out in Skia. This will allow for avoiding this copy in
// cases where the result image doesn't need to be resized.
memcpy(pixels, software_decoded_data_->data(),
software_decoded_data_->size());
return true;
}
void AndroidImageGenerator::DecodeImage() {
DoDecodeImage();
header_decoded_latch_.Signal();
fully_decoded_latch_.Signal();
}
void AndroidImageGenerator::DoDecodeImage() {
FML_DCHECK(g_flutter_jni_class);
FML_DCHECK(g_decode_image_method);
// Call FlutterJNI.decodeImage
JNIEnv* env = fml::jni::AttachCurrentThread();
// This task is run on the IO thread. Create a frame to ensure that all
// local JNI references used here are freed.
fml::jni::ScopedJavaLocalFrame scoped_local_reference_frame(env);
jobject direct_buffer =
env->NewDirectByteBuffer(const_cast<void*>(data_->data()), data_->size());
auto bitmap = std::make_unique<fml::jni::ScopedJavaGlobalRef<jobject>>(
env, env->CallStaticObjectMethod(g_flutter_jni_class->obj(),
g_decode_image_method, direct_buffer,
reinterpret_cast<long>(this)));
FML_CHECK(fml::jni::CheckException(env));
if (bitmap->is_null()) {
return;
}
AndroidBitmapInfo info;
[[maybe_unused]] int status;
if ((status = AndroidBitmap_getInfo(env, bitmap->obj(), &info)) < 0) {
FML_DLOG(ERROR) << "Failed to get bitmap info, status=" << status;
return;
}
FML_DCHECK(info.format == ANDROID_BITMAP_FORMAT_RGBA_8888);
// Lock the android buffer in a shared pointer
void* pixel_lock;
if ((status = AndroidBitmap_lockPixels(env, bitmap->obj(), &pixel_lock)) <
0) {
FML_DLOG(ERROR) << "Failed to lock pixels, error=" << status;
return;
}
SkData::ReleaseProc on_release = [](const void* ptr, void* context) -> void {
fml::jni::ScopedJavaGlobalRef<jobject>* bitmap =
reinterpret_cast<fml::jni::ScopedJavaGlobalRef<jobject>*>(context);
auto env = fml::jni::AttachCurrentThread();
AndroidBitmap_unlockPixels(env, bitmap->obj());
delete bitmap;
};
software_decoded_data_ = SkData::MakeWithProc(
pixel_lock, info.width * info.height * sizeof(uint32_t), on_release,
bitmap.release());
}
bool AndroidImageGenerator::Register(JNIEnv* env) {
g_flutter_jni_class = new fml::jni::ScopedJavaGlobalRef<jclass>(
env, env->FindClass("io/flutter/embedding/engine/FlutterJNI"));
FML_DCHECK(!g_flutter_jni_class->is_null());
g_decode_image_method = env->GetStaticMethodID(
g_flutter_jni_class->obj(), "decodeImage",
"(Ljava/nio/ByteBuffer;J)Landroid/graphics/Bitmap;");
FML_DCHECK(g_decode_image_method);
static const JNINativeMethod header_decoded_method = {
.name = "nativeImageHeaderCallback",
.signature = "(JII)V",
.fnPtr = reinterpret_cast<void*>(
&AndroidImageGenerator::NativeImageHeaderCallback),
};
if (env->RegisterNatives(g_flutter_jni_class->obj(), &header_decoded_method,
1) != 0) {
FML_LOG(ERROR)
<< "Failed to register FlutterJNI.nativeImageHeaderCallback method";
return false;
}
return true;
}
std::shared_ptr<ImageGenerator> AndroidImageGenerator::MakeFromData(
sk_sp<SkData> data,
fml::RefPtr<fml::TaskRunner> task_runner) {
std::shared_ptr<AndroidImageGenerator> generator(
new AndroidImageGenerator(std::move(data)));
fml::TaskRunner::RunNowOrPostTask(
task_runner, [generator]() { generator->DecodeImage(); });
if (generator->IsValidImageData()) {
return generator;
}
return nullptr;
}
void AndroidImageGenerator::NativeImageHeaderCallback(JNIEnv* env,
jclass jcaller,
jlong generator_address,
int width,
int height) {
AndroidImageGenerator* generator =
reinterpret_cast<AndroidImageGenerator*>(generator_address);
generator->image_info_ = SkImageInfo::Make(
width, height, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
generator->header_decoded_latch_.Signal();
}
bool AndroidImageGenerator::IsValidImageData() {
// The generator kicks off an IO task to decode everything, and calls to
// "GetInfo()" block until either the header has been decoded or decoding has
// failed, whichever is sooner. The decoder is initialized with a width and
// height of -1 and will update the dimensions if the image is able to be
// decoded.
return GetInfo().height() != -1;
}
} // namespace flutter