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

Commit 5513273

Browse files
authored
Reland: Avoid a copy in EncodeImage (#20003)
1 parent 7f5d044 commit 5513273

File tree

5 files changed

+126
-7
lines changed

5 files changed

+126
-7
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,7 @@ FILE: ../../../flutter/lib/ui/painting/image_descriptor.cc
339339
FILE: ../../../flutter/lib/ui/painting/image_descriptor.h
340340
FILE: ../../../flutter/lib/ui/painting/image_encoding.cc
341341
FILE: ../../../flutter/lib/ui/painting/image_encoding.h
342+
FILE: ../../../flutter/lib/ui/painting/image_encoding_unittests.cc
342343
FILE: ../../../flutter/lib/ui/painting/image_filter.cc
343344
FILE: ../../../flutter/lib/ui/painting/image_filter.h
344345
FILE: ../../../flutter/lib/ui/painting/image_shader.cc

lib/ui/BUILD.gn

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

180180
sources = [
181+
"painting/image_encoding_unittests.cc",
181182
"painting/vertices_unittests.cc",
182183
"window/pointer_data_packet_converter_unittests.cc",
183184
]

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
@@ -35,6 +35,13 @@ enum ImageByteFormat {
3535
kPNG,
3636
};
3737

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

5467
sk_sp<SkImage> ConvertToRasterUsingResourceContext(
@@ -222,9 +235,10 @@ void EncodeImageAndInvokeDataCallback(
222235
auto encode_task = [callback_task = std::move(callback_task), format,
223236
ui_task_runner](sk_sp<SkImage> raster_image) {
224237
sk_sp<SkData> encoded = EncodeImage(std::move(raster_image), format);
225-
ui_task_runner->PostTask(
226-
[callback_task = std::move(callback_task),
227-
encoded = std::move(encoded)] { callback_task(encoded); });
238+
ui_task_runner->PostTask([callback_task = std::move(callback_task),
239+
encoded = std::move(encoded)]() mutable {
240+
callback_task(std::move(encoded));
241+
});
228242
};
229243

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

0 commit comments

Comments
 (0)