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

Commit 1f35541

Browse files
committed
[Android] Speed up the method 'FlutterRenderer.getBitmap'
1 parent 891d4a3 commit 1f35541

File tree

7 files changed

+176
-54
lines changed

7 files changed

+176
-54
lines changed

shell/platform/android/platform_view_android_jni_impl.cc

Lines changed: 62 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ static fml::jni::ScopedJavaGlobalRef<jclass>* g_texture_wrapper_class = nullptr;
4949

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

52+
static fml::jni::ScopedJavaGlobalRef<jclass>* g_bitmap_class = nullptr;
53+
54+
static fml::jni::ScopedJavaGlobalRef<jclass>* g_bitmap_config_class = nullptr;
55+
5256
// Called By Native
5357

5458
static jmethodID g_flutter_callback_info_constructor = nullptr;
@@ -115,6 +119,12 @@ static jmethodID g_overlay_surface_id_method = nullptr;
115119

116120
static jmethodID g_overlay_surface_surface_method = nullptr;
117121

122+
static jmethodID g_bitmap_create_bitmap_method = nullptr;
123+
124+
static jmethodID g_bitmap_copy_pixels_from_buffer_method = nullptr;
125+
126+
static jmethodID g_bitmap_config_value_of = nullptr;
127+
118128
// Mutators
119129
static fml::jni::ScopedJavaGlobalRef<jclass>* g_mutators_stack_class = nullptr;
120130
static jmethodID g_mutators_stack_init_method = nullptr;
@@ -337,70 +347,31 @@ static jobject GetBitmap(JNIEnv* env, jobject jcaller, jlong shell_holder) {
337347
return nullptr;
338348
}
339349

340-
const SkISize& frame_size = screenshot.frame_size;
341-
jsize pixels_size = frame_size.width() * frame_size.height();
342-
jintArray pixels_array = env->NewIntArray(pixels_size);
343-
if (pixels_array == nullptr) {
344-
return nullptr;
345-
}
346-
347-
jint* pixels = env->GetIntArrayElements(pixels_array, nullptr);
348-
if (pixels == nullptr) {
349-
return nullptr;
350-
}
351-
352-
auto* pixels_src = static_cast<const int32_t*>(screenshot.data->data());
353-
354-
// Our configuration of Skia does not support rendering to the
355-
// BitmapConfig.ARGB_8888 format expected by android.graphics.Bitmap.
356-
// Convert from kRGBA_8888 to kBGRA_8888 (equivalent to ARGB_8888).
357-
for (int i = 0; i < pixels_size; i++) {
358-
int32_t src_pixel = pixels_src[i];
359-
uint8_t* src_bytes = reinterpret_cast<uint8_t*>(&src_pixel);
360-
std::swap(src_bytes[0], src_bytes[2]);
361-
pixels[i] = src_pixel;
362-
}
363-
364-
env->ReleaseIntArrayElements(pixels_array, pixels, 0);
365-
366-
jclass bitmap_class = env->FindClass("android/graphics/Bitmap");
367-
if (bitmap_class == nullptr) {
368-
return nullptr;
369-
}
370-
371-
jmethodID create_bitmap = env->GetStaticMethodID(
372-
bitmap_class, "createBitmap",
373-
"([IIILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
374-
if (create_bitmap == nullptr) {
375-
return nullptr;
376-
}
377-
378-
jclass bitmap_config_class = env->FindClass("android/graphics/Bitmap$Config");
379-
if (bitmap_config_class == nullptr) {
380-
return nullptr;
381-
}
382-
383-
jmethodID bitmap_config_value_of = env->GetStaticMethodID(
384-
bitmap_config_class, "valueOf",
385-
"(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;");
386-
if (bitmap_config_value_of == nullptr) {
387-
return nullptr;
388-
}
389-
390350
jstring argb = env->NewStringUTF("ARGB_8888");
391351
if (argb == nullptr) {
392352
return nullptr;
393353
}
394354

395355
jobject bitmap_config = env->CallStaticObjectMethod(
396-
bitmap_config_class, bitmap_config_value_of, argb);
356+
g_bitmap_config_class->obj(), g_bitmap_config_value_of, argb);
397357
if (bitmap_config == nullptr) {
398358
return nullptr;
399359
}
400360

401-
return env->CallStaticObjectMethod(bitmap_class, create_bitmap, pixels_array,
402-
frame_size.width(), frame_size.height(),
403-
bitmap_config);
361+
auto bitmap = env->CallStaticObjectMethod(
362+
g_bitmap_class->obj(), g_bitmap_create_bitmap_method,
363+
screenshot.frame_size.width(), screenshot.frame_size.height(),
364+
bitmap_config);
365+
366+
fml::jni::ScopedJavaLocalRef<jobject> buffer(
367+
env,
368+
env->NewDirectByteBuffer(const_cast<uint8_t*>(screenshot.data->bytes()),
369+
screenshot.data->size()));
370+
371+
env->CallVoidMethod(bitmap, g_bitmap_copy_pixels_from_buffer_method,
372+
buffer.obj());
373+
374+
return bitmap;
404375
}
405376

406377
static void DispatchPlatformMessage(JNIEnv* env,
@@ -945,6 +916,43 @@ bool RegisterApi(JNIEnv* env) {
945916
return false;
946917
}
947918

919+
g_bitmap_class = new fml::jni::ScopedJavaGlobalRef<jclass>(
920+
env, env->FindClass("android/graphics/Bitmap"));
921+
if (g_bitmap_class->is_null()) {
922+
FML_LOG(ERROR) << "Could not locate Bitmap Class";
923+
return false;
924+
}
925+
926+
g_bitmap_create_bitmap_method = env->GetStaticMethodID(
927+
g_bitmap_class->obj(), "createBitmap",
928+
"(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
929+
if (g_bitmap_create_bitmap_method == nullptr) {
930+
FML_LOG(ERROR) << "Could not locate Bitmap.createBitmap method";
931+
return false;
932+
}
933+
934+
g_bitmap_copy_pixels_from_buffer_method = env->GetMethodID(
935+
g_bitmap_class->obj(), "copyPixelsFromBuffer", "(Ljava/nio/Buffer;)V");
936+
if (g_bitmap_copy_pixels_from_buffer_method == nullptr) {
937+
FML_LOG(ERROR) << "Could not locate Bitmap.copyPixelsFromBuffer method";
938+
return false;
939+
}
940+
941+
g_bitmap_config_class = new fml::jni::ScopedJavaGlobalRef<jclass>(
942+
env, env->FindClass("android/graphics/Bitmap$Config"));
943+
if (g_bitmap_config_class->is_null()) {
944+
FML_LOG(ERROR) << "Could not locate Bitmap.Config Class";
945+
return false;
946+
}
947+
948+
g_bitmap_config_value_of = env->GetStaticMethodID(
949+
g_bitmap_config_class->obj(), "valueOf",
950+
"(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;");
951+
if (g_bitmap_config_value_of == nullptr) {
952+
FML_LOG(ERROR) << "Could not locate Bitmap.Config.valueOf method";
953+
return false;
954+
}
955+
948956
return true;
949957
}
950958

testing/scenario_app/android/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ _android_sources = [
1010
"app/src/androidTest/java/dev/flutter/scenarios/EngineLaunchE2ETest.java",
1111
"app/src/androidTest/java/dev/flutter/scenarios/ExampleInstrumentedTest.java",
1212
"app/src/androidTest/java/dev/flutter/scenariosui/ExternalTextureTests.java",
13+
"app/src/androidTest/java/dev/flutter/scenariosui/GetBitmapTests.java",
1314
"app/src/androidTest/java/dev/flutter/scenariosui/MemoryLeakTests.java",
1415
"app/src/androidTest/java/dev/flutter/scenariosui/PlatformTextureUiTests.java",
1516
"app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewUiTests.java",
@@ -21,6 +22,7 @@ _android_sources = [
2122
"app/src/androidTest/java/dev/flutter/scenariosui/ScreenshotUtil.java",
2223
"app/src/androidTest/java/dev/flutter/scenariosui/SpawnEngineTests.java",
2324
"app/src/main/AndroidManifest.xml",
25+
"app/src/main/java/dev/flutter/scenarios/GetBitmapActivity.java",
2426
"app/src/main/java/dev/flutter/scenarios/PlatformViewsActivity.java",
2527
"app/src/main/java/dev/flutter/scenarios/SpawnedEngineActivity.java",
2628
"app/src/main/java/dev/flutter/scenarios/StrictModeFlutterActivity.java",
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package dev.flutter.scenariosui;
6+
7+
import static org.junit.Assert.*;
8+
9+
import android.content.Intent;
10+
import android.graphics.Bitmap;
11+
import androidx.annotation.NonNull;
12+
import androidx.test.filters.LargeTest;
13+
import androidx.test.rule.ActivityTestRule;
14+
import androidx.test.runner.AndroidJUnit4;
15+
import dev.flutter.scenarios.GetBitmapActivity;
16+
import org.junit.Rule;
17+
import org.junit.Test;
18+
import org.junit.runner.RunWith;
19+
20+
@RunWith(AndroidJUnit4.class)
21+
@LargeTest
22+
public class GetBitmapTests {
23+
@Rule @NonNull
24+
public ActivityTestRule<GetBitmapActivity> activityRule =
25+
new ActivityTestRule<>(
26+
GetBitmapActivity.class, /*initialTouchMode=*/ false, /*launchActivity=*/ false);
27+
28+
@Test
29+
public void getBitmap() throws Exception {
30+
Intent intent = new Intent(Intent.ACTION_MAIN);
31+
intent.putExtra("scenario_name", "get_bitmap");
32+
GetBitmapActivity activity = activityRule.launchActivity(intent);
33+
Bitmap bitmap = activity.getBitmap();
34+
35+
assertEquals(bitmap.getPixel(10, 10), 0xFFFF0000);
36+
assertEquals(bitmap.getPixel(10, bitmap.getHeight() - 10), 0xFF0000FF);
37+
}
38+
}

testing/scenario_app/android/app/src/main/AndroidManifest.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,17 @@
6060
<category android:name="android.intent.category.LAUNCHER" />
6161
</intent-filter>
6262
</activity>
63+
<activity
64+
android:name=".GetBitmapActivity"
65+
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
66+
android:hardwareAccelerated="true"
67+
android:launchMode="singleTop"
68+
android:windowSoftInputMode="adjustResize"
69+
android:exported="true">
70+
<intent-filter>
71+
<action android:name="android.intent.action.MAIN" />
72+
<category android:name="android.intent.category.LAUNCHER" />
73+
</intent-filter>
74+
</activity>
6375
</application>
6476
</manifest>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package dev.flutter.scenarios;
6+
7+
import android.graphics.Bitmap;
8+
import androidx.annotation.Nullable;
9+
10+
public class GetBitmapActivity extends TestActivity {
11+
12+
@Nullable private volatile Bitmap bitmap;
13+
14+
@Nullable
15+
public Bitmap getBitmap() {
16+
waitUntilFlutterRendered();
17+
return bitmap;
18+
}
19+
20+
@Nullable
21+
protected void notifyFlutterRendered() {
22+
bitmap = getFlutterEngine().getRenderer().getBitmap();
23+
super.notifyFlutterRendered();
24+
}
25+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:ui';
6+
7+
import 'scenario.dart';
8+
9+
/// Displays a platform texture with the given width and height.
10+
class GetBitmapScenario extends Scenario {
11+
/// Creates the GetBitmap scenario.
12+
///
13+
/// The [dispatcher] parameter must not be null.
14+
GetBitmapScenario(PlatformDispatcher dispatcher)
15+
: super(dispatcher);
16+
17+
@override
18+
void onBeginFrame(Duration duration) {
19+
final PictureRecorder recorder = PictureRecorder();
20+
final Canvas canvas = Canvas(recorder);
21+
canvas.drawRect(Rect.fromLTWH(0, 0, window.physicalSize.width, 300),
22+
Paint()..color = const Color(0xFFFF0000));
23+
canvas.drawRect(
24+
Rect.fromLTWH(0, window.physicalSize.height - 300,
25+
window.physicalSize.width, 300),
26+
Paint()..color = const Color(0xFF0000FF));
27+
final Picture picture = recorder.endRecording();
28+
final SceneBuilder builder = SceneBuilder();
29+
builder.addPicture(Offset.zero, picture);
30+
final Scene scene = builder.build();
31+
window.render(scene);
32+
picture.dispose();
33+
scene.dispose();
34+
}
35+
}

testing/scenario_app/lib/src/scenarios.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:ui';
66

77
import 'animated_color_square.dart';
88
import 'bogus_font_text.dart';
9+
import 'get_bitmap_scenario.dart';
910
import 'initial_route_reply.dart';
1011
import 'locale_initialization.dart';
1112
import 'platform_view.dart';
@@ -52,6 +53,7 @@ Map<String, ScenarioFactory> _scenarios = <String, ScenarioFactory>{
5253
'spawn_engine_works' : () => BogusFontText(PlatformDispatcher.instance),
5354
'pointer_events': () => TouchesScenario(PlatformDispatcher.instance),
5455
'display_texture': () => DisplayTexture(PlatformDispatcher.instance),
56+
'get_bitmap': () => GetBitmapScenario(PlatformDispatcher.instance),
5557
};
5658

5759
Map<String, dynamic> _currentScenarioParams = <String, dynamic>{};

0 commit comments

Comments
 (0)