Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

[Android] Speed up the method 'FlutterRenderer.getBitmap' #37342

Merged
merged 1 commit into from
Nov 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 62 additions & 54 deletions shell/platform/android/platform_view_android_jni_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ static fml::jni::ScopedJavaGlobalRef<jclass>* g_texture_wrapper_class = nullptr;

static fml::jni::ScopedJavaGlobalRef<jclass>* g_java_long_class = nullptr;

static fml::jni::ScopedJavaGlobalRef<jclass>* g_bitmap_class = nullptr;

static fml::jni::ScopedJavaGlobalRef<jclass>* g_bitmap_config_class = nullptr;

// Called By Native

static jmethodID g_flutter_callback_info_constructor = nullptr;
Expand Down Expand Up @@ -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<jclass>* g_mutators_stack_class = nullptr;
static jmethodID g_mutators_stack_init_method = nullptr;
Expand Down Expand Up @@ -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<const int32_t*>(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<uint8_t*>(&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<jobject> buffer(
env,
env->NewDirectByteBuffer(const_cast<uint8_t*>(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,
Expand Down Expand Up @@ -945,6 +916,43 @@ bool RegisterApi(JNIEnv* env) {
return false;
}

g_bitmap_class = new fml::jni::ScopedJavaGlobalRef<jclass>(
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<jclass>(
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;
}

Expand Down
2 changes: 2 additions & 0 deletions testing/scenario_app/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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<GetBitmapActivity> 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);
}
}
12 changes: 12 additions & 0 deletions testing/scenario_app/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,17 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".GetBitmapActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -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();
}
}
35 changes: 35 additions & 0 deletions testing/scenario_app/lib/src/get_bitmap_scenario.dart
Original file line number Diff line number Diff line change
@@ -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();
}
}
2 changes: 2 additions & 0 deletions testing/scenario_app/lib/src/scenarios.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -52,6 +53,7 @@ Map<String, ScenarioFactory> _scenarios = <String, ScenarioFactory>{
'spawn_engine_works' : () => BogusFontText(PlatformDispatcher.instance),
'pointer_events': () => TouchesScenario(PlatformDispatcher.instance),
'display_texture': () => DisplayTexture(PlatformDispatcher.instance),
'get_bitmap': () => GetBitmapScenario(PlatformDispatcher.instance),
};

Map<String, dynamic> _currentScenarioParams = <String, dynamic>{};
Expand Down