From 66c6c8afbcf8de942da222202f8cd95321465eef Mon Sep 17 00:00:00 2001 From: ColdPaleLight Date: Mon, 7 Nov 2022 20:23:23 +0800 Subject: [PATCH] [Android] Speed up the method 'FlutterRenderer.getBitmap' --- .../android/platform_view_android_jni_impl.cc | 116 ++++++++++-------- testing/scenario_app/android/BUILD.gn | 2 + .../flutter/scenariosui/GetBitmapTests.java | 38 ++++++ .../android/app/src/main/AndroidManifest.xml | 12 ++ .../flutter/scenarios/GetBitmapActivity.java | 25 ++++ .../lib/src/get_bitmap_scenario.dart | 35 ++++++ testing/scenario_app/lib/src/scenarios.dart | 2 + 7 files changed, 176 insertions(+), 54 deletions(-) create mode 100644 testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/GetBitmapTests.java create mode 100644 testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/GetBitmapActivity.java create mode 100644 testing/scenario_app/lib/src/get_bitmap_scenario.dart diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index 24516c75e937d..9cc05789d943a 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -49,6 +49,10 @@ static fml::jni::ScopedJavaGlobalRef* g_texture_wrapper_class = nullptr; static fml::jni::ScopedJavaGlobalRef* g_java_long_class = nullptr; +static fml::jni::ScopedJavaGlobalRef* g_bitmap_class = nullptr; + +static fml::jni::ScopedJavaGlobalRef* g_bitmap_config_class = nullptr; + // Called By Native static jmethodID g_flutter_callback_info_constructor = nullptr; @@ -115,6 +119,12 @@ static jmethodID g_overlay_surface_id_method = nullptr; static jmethodID g_overlay_surface_surface_method = nullptr; +static jmethodID g_bitmap_create_bitmap_method = nullptr; + +static jmethodID g_bitmap_copy_pixels_from_buffer_method = nullptr; + +static jmethodID g_bitmap_config_value_of = nullptr; + // Mutators static fml::jni::ScopedJavaGlobalRef* g_mutators_stack_class = nullptr; static jmethodID g_mutators_stack_init_method = nullptr; @@ -337,70 +347,31 @@ static jobject GetBitmap(JNIEnv* env, jobject jcaller, jlong shell_holder) { return nullptr; } - const SkISize& frame_size = screenshot.frame_size; - jsize pixels_size = frame_size.width() * frame_size.height(); - jintArray pixels_array = env->NewIntArray(pixels_size); - if (pixels_array == nullptr) { - return nullptr; - } - - jint* pixels = env->GetIntArrayElements(pixels_array, nullptr); - if (pixels == nullptr) { - return nullptr; - } - - auto* pixels_src = static_cast(screenshot.data->data()); - - // Our configuration of Skia does not support rendering to the - // BitmapConfig.ARGB_8888 format expected by android.graphics.Bitmap. - // Convert from kRGBA_8888 to kBGRA_8888 (equivalent to ARGB_8888). - for (int i = 0; i < pixels_size; i++) { - int32_t src_pixel = pixels_src[i]; - uint8_t* src_bytes = reinterpret_cast(&src_pixel); - std::swap(src_bytes[0], src_bytes[2]); - pixels[i] = src_pixel; - } - - env->ReleaseIntArrayElements(pixels_array, pixels, 0); - - jclass bitmap_class = env->FindClass("android/graphics/Bitmap"); - if (bitmap_class == nullptr) { - return nullptr; - } - - jmethodID create_bitmap = env->GetStaticMethodID( - bitmap_class, "createBitmap", - "([IIILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;"); - if (create_bitmap == nullptr) { - return nullptr; - } - - jclass bitmap_config_class = env->FindClass("android/graphics/Bitmap$Config"); - if (bitmap_config_class == nullptr) { - return nullptr; - } - - jmethodID bitmap_config_value_of = env->GetStaticMethodID( - bitmap_config_class, "valueOf", - "(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;"); - if (bitmap_config_value_of == nullptr) { - return nullptr; - } - jstring argb = env->NewStringUTF("ARGB_8888"); if (argb == nullptr) { return nullptr; } jobject bitmap_config = env->CallStaticObjectMethod( - bitmap_config_class, bitmap_config_value_of, argb); + g_bitmap_config_class->obj(), g_bitmap_config_value_of, argb); if (bitmap_config == nullptr) { return nullptr; } - return env->CallStaticObjectMethod(bitmap_class, create_bitmap, pixels_array, - frame_size.width(), frame_size.height(), - bitmap_config); + auto bitmap = env->CallStaticObjectMethod( + g_bitmap_class->obj(), g_bitmap_create_bitmap_method, + screenshot.frame_size.width(), screenshot.frame_size.height(), + bitmap_config); + + fml::jni::ScopedJavaLocalRef buffer( + env, + env->NewDirectByteBuffer(const_cast(screenshot.data->bytes()), + screenshot.data->size())); + + env->CallVoidMethod(bitmap, g_bitmap_copy_pixels_from_buffer_method, + buffer.obj()); + + return bitmap; } static void DispatchPlatformMessage(JNIEnv* env, @@ -945,6 +916,43 @@ bool RegisterApi(JNIEnv* env) { return false; } + g_bitmap_class = new fml::jni::ScopedJavaGlobalRef( + env, env->FindClass("android/graphics/Bitmap")); + if (g_bitmap_class->is_null()) { + FML_LOG(ERROR) << "Could not locate Bitmap Class"; + return false; + } + + g_bitmap_create_bitmap_method = env->GetStaticMethodID( + g_bitmap_class->obj(), "createBitmap", + "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;"); + if (g_bitmap_create_bitmap_method == nullptr) { + FML_LOG(ERROR) << "Could not locate Bitmap.createBitmap method"; + return false; + } + + g_bitmap_copy_pixels_from_buffer_method = env->GetMethodID( + g_bitmap_class->obj(), "copyPixelsFromBuffer", "(Ljava/nio/Buffer;)V"); + if (g_bitmap_copy_pixels_from_buffer_method == nullptr) { + FML_LOG(ERROR) << "Could not locate Bitmap.copyPixelsFromBuffer method"; + return false; + } + + g_bitmap_config_class = new fml::jni::ScopedJavaGlobalRef( + env, env->FindClass("android/graphics/Bitmap$Config")); + if (g_bitmap_config_class->is_null()) { + FML_LOG(ERROR) << "Could not locate Bitmap.Config Class"; + return false; + } + + g_bitmap_config_value_of = env->GetStaticMethodID( + g_bitmap_config_class->obj(), "valueOf", + "(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;"); + if (g_bitmap_config_value_of == nullptr) { + FML_LOG(ERROR) << "Could not locate Bitmap.Config.valueOf method"; + return false; + } + return true; } diff --git a/testing/scenario_app/android/BUILD.gn b/testing/scenario_app/android/BUILD.gn index e2fa53baf8372..200c90784f7da 100644 --- a/testing/scenario_app/android/BUILD.gn +++ b/testing/scenario_app/android/BUILD.gn @@ -10,6 +10,7 @@ _android_sources = [ "app/src/androidTest/java/dev/flutter/scenarios/EngineLaunchE2ETest.java", "app/src/androidTest/java/dev/flutter/scenarios/ExampleInstrumentedTest.java", "app/src/androidTest/java/dev/flutter/scenariosui/ExternalTextureTests.java", + "app/src/androidTest/java/dev/flutter/scenariosui/GetBitmapTests.java", "app/src/androidTest/java/dev/flutter/scenariosui/MemoryLeakTests.java", "app/src/androidTest/java/dev/flutter/scenariosui/PlatformTextureUiTests.java", "app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewUiTests.java", @@ -21,6 +22,7 @@ _android_sources = [ "app/src/androidTest/java/dev/flutter/scenariosui/ScreenshotUtil.java", "app/src/androidTest/java/dev/flutter/scenariosui/SpawnEngineTests.java", "app/src/main/AndroidManifest.xml", + "app/src/main/java/dev/flutter/scenarios/GetBitmapActivity.java", "app/src/main/java/dev/flutter/scenarios/PlatformViewsActivity.java", "app/src/main/java/dev/flutter/scenarios/SpawnedEngineActivity.java", "app/src/main/java/dev/flutter/scenarios/StrictModeFlutterActivity.java", diff --git a/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/GetBitmapTests.java b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/GetBitmapTests.java new file mode 100644 index 0000000000000..4b44da007f482 --- /dev/null +++ b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/GetBitmapTests.java @@ -0,0 +1,38 @@ +// 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. + +package dev.flutter.scenariosui; + +import static org.junit.Assert.*; + +import android.content.Intent; +import android.graphics.Bitmap; +import androidx.annotation.NonNull; +import androidx.test.filters.LargeTest; +import androidx.test.rule.ActivityTestRule; +import androidx.test.runner.AndroidJUnit4; +import dev.flutter.scenarios.GetBitmapActivity; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class GetBitmapTests { + @Rule @NonNull + public ActivityTestRule activityRule = + new ActivityTestRule<>( + GetBitmapActivity.class, /*initialTouchMode=*/ false, /*launchActivity=*/ false); + + @Test + public void getBitmap() throws Exception { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.putExtra("scenario_name", "get_bitmap"); + GetBitmapActivity activity = activityRule.launchActivity(intent); + Bitmap bitmap = activity.getBitmap(); + + assertEquals(bitmap.getPixel(10, 10), 0xFFFF0000); + assertEquals(bitmap.getPixel(10, bitmap.getHeight() - 10), 0xFF0000FF); + } +} diff --git a/testing/scenario_app/android/app/src/main/AndroidManifest.xml b/testing/scenario_app/android/app/src/main/AndroidManifest.xml index 09f440887c41d..7952e14f9e799 100644 --- a/testing/scenario_app/android/app/src/main/AndroidManifest.xml +++ b/testing/scenario_app/android/app/src/main/AndroidManifest.xml @@ -60,5 +60,17 @@ + + + + + + diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/GetBitmapActivity.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/GetBitmapActivity.java new file mode 100644 index 0000000000000..78bd2b9fa26ea --- /dev/null +++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/GetBitmapActivity.java @@ -0,0 +1,25 @@ +// 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. + +package dev.flutter.scenarios; + +import android.graphics.Bitmap; +import androidx.annotation.Nullable; + +public class GetBitmapActivity extends TestActivity { + + @Nullable private volatile Bitmap bitmap; + + @Nullable + public Bitmap getBitmap() { + waitUntilFlutterRendered(); + return bitmap; + } + + @Nullable + protected void notifyFlutterRendered() { + bitmap = getFlutterEngine().getRenderer().getBitmap(); + super.notifyFlutterRendered(); + } +} diff --git a/testing/scenario_app/lib/src/get_bitmap_scenario.dart b/testing/scenario_app/lib/src/get_bitmap_scenario.dart new file mode 100644 index 0000000000000..01fb99c5e9b9e --- /dev/null +++ b/testing/scenario_app/lib/src/get_bitmap_scenario.dart @@ -0,0 +1,35 @@ +// 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. + +import 'dart:ui'; + +import 'scenario.dart'; + +/// A scenario with red on top and blue on the bottom. +class GetBitmapScenario extends Scenario { + /// Creates the GetBitmap scenario. + /// + /// The [dispatcher] parameter must not be null. + GetBitmapScenario(PlatformDispatcher dispatcher) + : super(dispatcher); + + @override + void onBeginFrame(Duration duration) { + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + canvas.drawRect(Rect.fromLTWH(0, 0, window.physicalSize.width, 300), + Paint()..color = const Color(0xFFFF0000)); + canvas.drawRect( + Rect.fromLTWH(0, window.physicalSize.height - 300, + window.physicalSize.width, 300), + Paint()..color = const Color(0xFF0000FF)); + final Picture picture = recorder.endRecording(); + final SceneBuilder builder = SceneBuilder(); + builder.addPicture(Offset.zero, picture); + final Scene scene = builder.build(); + window.render(scene); + picture.dispose(); + scene.dispose(); + } +} diff --git a/testing/scenario_app/lib/src/scenarios.dart b/testing/scenario_app/lib/src/scenarios.dart index ad353fa83771e..cbf4e6f528658 100644 --- a/testing/scenario_app/lib/src/scenarios.dart +++ b/testing/scenario_app/lib/src/scenarios.dart @@ -6,6 +6,7 @@ import 'dart:ui'; import 'animated_color_square.dart'; import 'bogus_font_text.dart'; +import 'get_bitmap_scenario.dart'; import 'initial_route_reply.dart'; import 'locale_initialization.dart'; import 'platform_view.dart'; @@ -52,6 +53,7 @@ Map _scenarios = { 'spawn_engine_works' : () => BogusFontText(PlatformDispatcher.instance), 'pointer_events': () => TouchesScenario(PlatformDispatcher.instance), 'display_texture': () => DisplayTexture(PlatformDispatcher.instance), + 'get_bitmap': () => GetBitmapScenario(PlatformDispatcher.instance), }; Map _currentScenarioParams = {};