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

Commit 5a7e3b2

Browse files
committed
Reland: Avoid a copy in EncodeImage
1 parent 798182e commit 5a7e3b2

File tree

4 files changed

+123
-7
lines changed

4 files changed

+123
-7
lines changed

lib/ui/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ if (enable_unittests) {
182182
public_configs = [ "//flutter:export_dynamic_symbols" ]
183183

184184
sources = [
185+
"painting/image_encoding_unittests.cc",
185186
"painting/vertices_unittests.cc",
186187
"window/pointer_data_packet_converter_unittests.cc",
187188
]

lib/ui/fixtures/ui_test.dart

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,29 @@ void frameCallback(FrameInfo info) {
4444
@pragma('vm:entry-point')
4545
void messageCallback(dynamic data) {
4646
}
47+
48+
49+
// Draw a circle on a Canvas that has a PictureRecorder. Take the image from
50+
// the PictureRecorder, and encode it as png. Check that the png data is
51+
// backed by an external Uint8List.
52+
@pragma('vm:entry-point')
53+
Future<void> encodeImageProducesExternalUint8List() async {
54+
final PictureRecorder pictureRecorder = PictureRecorder();
55+
final Canvas canvas = Canvas(pictureRecorder);
56+
final Paint paint = Paint()
57+
..color = Color.fromRGBO(255, 255, 255, 1.0)
58+
..style = PaintingStyle.fill;
59+
final Offset c = Offset(50.0, 50.0);
60+
canvas.drawCircle(c, 25.0, paint);
61+
final Picture picture = pictureRecorder.endRecording();
62+
final Image image = await picture.toImage(100, 100);
63+
_encodeImage(image, ImageByteFormat.png.index, (Uint8List result) {
64+
// The buffer should be non-null and writable.
65+
result[0] = 0;
66+
// The buffer should be external typed data.
67+
_validateExternal(result);
68+
});
69+
}
70+
void _encodeImage(Image i, int format, void Function(Uint8List result))
71+
native 'EncodeImage';
72+
void _validateExternal(Uint8List result) native 'ValidateExternal';

lib/ui/painting/image_encoding.cc

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ enum ImageByteFormat {
3636
kPNG,
3737
};
3838

39+
void FinalizeSkData(void* isolate_callback_data,
40+
Dart_WeakPersistentHandle handle,
41+
void* peer) {
42+
SkData* buffer = reinterpret_cast<SkData*>(peer);
43+
buffer->unref();
44+
}
45+
3946
void InvokeDataCallback(std::unique_ptr<DartPersistentValue> callback,
4047
sk_sp<SkData> buffer) {
4148
std::shared_ptr<tonic::DartState> dart_state = callback->dart_state().lock();
@@ -45,11 +52,17 @@ void InvokeDataCallback(std::unique_ptr<DartPersistentValue> callback,
4552
tonic::DartState::Scope scope(dart_state);
4653
if (!buffer) {
4754
DartInvoke(callback->value(), {Dart_Null()});
48-
} else {
49-
Dart_Handle dart_data = tonic::DartConverter<tonic::Uint8List>::ToDart(
50-
buffer->bytes(), buffer->size());
51-
DartInvoke(callback->value(), {dart_data});
55+
return;
5256
}
57+
// Skia will not modify the buffer, and it is backed by memory that is
58+
// read/write, so Dart can be given direct access to the buffer through an
59+
// external Uint8List.
60+
void* bytes = const_cast<void*>(buffer->data());
61+
const intptr_t length = buffer->size();
62+
void* peer = reinterpret_cast<void*>(buffer.release());
63+
Dart_Handle dart_data = Dart_NewExternalTypedDataWithFinalizer(
64+
Dart_TypedData_kUint8, bytes, length, peer, length, FinalizeSkData);
65+
DartInvoke(callback->value(), {dart_data});
5366
}
5467

5568
sk_sp<SkImage> ConvertToRasterUsingResourceContext(
@@ -223,9 +236,10 @@ void EncodeImageAndInvokeDataCallback(
223236
auto encode_task = [callback_task = std::move(callback_task), format,
224237
ui_task_runner](sk_sp<SkImage> raster_image) {
225238
sk_sp<SkData> encoded = EncodeImage(std::move(raster_image), format);
226-
ui_task_runner->PostTask(
227-
[callback_task = std::move(callback_task),
228-
encoded = std::move(encoded)] { callback_task(encoded); });
239+
ui_task_runner->PostTask([callback_task = std::move(callback_task),
240+
encoded = std::move(encoded)]() mutable {
241+
callback_task(std::move(encoded));
242+
});
229243
};
230244

231245
ConvertImageToRaster(std::move(image), encode_task, raster_task_runner,
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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+
#include "flutter/common/task_runners.h"
6+
#include "flutter/fml/synchronization/waitable_event.h"
7+
#include "flutter/lib/ui/painting/image_encoding.h"
8+
#include "flutter/runtime/dart_vm.h"
9+
#include "flutter/shell/common/shell_test.h"
10+
#include "flutter/shell/common/thread_host.h"
11+
#include "flutter/testing/testing.h"
12+
13+
namespace flutter {
14+
namespace testing {
15+
16+
TEST_F(ShellTest, EncodeImageGivesExternalTypedData) {
17+
fml::AutoResetWaitableEvent message_latch;
18+
19+
auto nativeEncodeImage = [&](Dart_NativeArguments args) {
20+
auto image_handle = Dart_GetNativeArgument(args, 0);
21+
auto format_handle = Dart_GetNativeArgument(args, 1);
22+
auto callback_handle = Dart_GetNativeArgument(args, 2);
23+
24+
intptr_t peer = 0;
25+
Dart_Handle result = Dart_GetNativeInstanceField(
26+
image_handle, tonic::DartWrappable::kPeerIndex, &peer);
27+
ASSERT_FALSE(Dart_IsError(result));
28+
CanvasImage* canvas_image = reinterpret_cast<CanvasImage*>(peer);
29+
30+
int64_t format = -1;
31+
result = Dart_IntegerToInt64(format_handle, &format);
32+
ASSERT_FALSE(Dart_IsError(result));
33+
34+
result = EncodeImage(canvas_image, format, callback_handle);
35+
ASSERT_TRUE(Dart_IsNull(result));
36+
};
37+
38+
auto nativeValidateExternal = [&](Dart_NativeArguments args) {
39+
auto handle = Dart_GetNativeArgument(args, 0);
40+
41+
auto typed_data_type = Dart_GetTypeOfExternalTypedData(handle);
42+
EXPECT_EQ(typed_data_type, Dart_TypedData_kUint8);
43+
44+
message_latch.Signal();
45+
};
46+
47+
Settings settings = CreateSettingsForFixture();
48+
TaskRunners task_runners("test", // label
49+
GetCurrentTaskRunner(), // platform
50+
CreateNewThread(), // raster
51+
CreateNewThread(), // ui
52+
CreateNewThread() // io
53+
);
54+
55+
AddNativeCallback("EncodeImage", CREATE_NATIVE_ENTRY(nativeEncodeImage));
56+
AddNativeCallback("ValidateExternal",
57+
CREATE_NATIVE_ENTRY(nativeValidateExternal));
58+
59+
std::unique_ptr<Shell> shell =
60+
CreateShell(std::move(settings), std::move(task_runners));
61+
62+
ASSERT_TRUE(shell->IsSetup());
63+
auto configuration = RunConfiguration::InferFromSettings(settings);
64+
configuration.SetEntrypoint("encodeImageProducesExternalUint8List");
65+
66+
shell->RunEngine(std::move(configuration), [&](auto result) {
67+
ASSERT_EQ(result, Engine::RunStatus::Success);
68+
});
69+
70+
message_latch.Wait();
71+
DestroyShell(std::move(shell), std::move(task_runners));
72+
}
73+
74+
} // namespace testing
75+
} // namespace flutter

0 commit comments

Comments
 (0)