Skip to content

Commit

Permalink
[Android] Speed up the method 'FlutterRenderer.getBitmap' (flutter#37342
Browse files Browse the repository at this point in the history
)
  • Loading branch information
ColdPaleLight authored Nov 7, 2022
1 parent 0075e10 commit 4e03d05
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 54 deletions.
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

0 comments on commit 4e03d05

Please sign in to comment.