Skip to content

Commit f9acd08

Browse files
authored
Avoid a copy in EncodeImage (flutter#19504)
1 parent 39e98d2 commit f9acd08

File tree

6 files changed

+123
-10
lines changed

6 files changed

+123
-10
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ FILE: ../../../flutter/lib/ui/painting/image_decoder.h
337337
FILE: ../../../flutter/lib/ui/painting/image_decoder_unittests.cc
338338
FILE: ../../../flutter/lib/ui/painting/image_encoding.cc
339339
FILE: ../../../flutter/lib/ui/painting/image_encoding.h
340+
FILE: ../../../flutter/lib/ui/painting/image_encoding_unittests.cc
340341
FILE: ../../../flutter/lib/ui/painting/image_filter.cc
341342
FILE: ../../../flutter/lib/ui/painting/image_filter.h
342343
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
@@ -161,6 +161,7 @@ if (current_toolchain == host_toolchain) {
161161

162162
sources = [
163163
"painting/image_decoder_unittests.cc",
164+
"painting/image_encoding_unittests.cc",
164165
"painting/vertices_unittests.cc",
165166
"window/pointer_data_packet_converter_unittests.cc",
166167
]

lib/ui/fixtures/ui_test.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,24 @@ 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, _validateExternal);
64+
}
65+
void _encodeImage(Image i, int format, void Function(Uint8List result))
66+
native 'EncodeImage';
67+
void _validateExternal(Uint8List result) native 'ValidateExternal';

lib/ui/painting.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1600,17 +1600,20 @@ class Image extends NativeFieldWrapperClass2 {
16001600
/// The number of image pixels along the image's vertical axis.
16011601
int get height native 'Image_height';
16021602

1603-
/// Converts the [Image] object into a byte array.
1603+
/// Converts the [Image] object into a read-only byte array.
16041604
///
16051605
/// The [format] argument specifies the format in which the bytes will be
16061606
/// returned.
16071607
///
16081608
/// Returns a future that completes with the binary image data or an error
1609-
/// if encoding fails.
1609+
/// if encoding fails. Note that attempting to write to the returned
1610+
/// [ByteData] will result in an [UnsupportedError] exception.
16101611
Future<ByteData?> toByteData({ImageByteFormat format = ImageByteFormat.rawRgba}) {
16111612
return _futurize((_Callback<ByteData> callback) {
16121613
return _toByteData(format.index, (Uint8List? encoded) {
1613-
callback(encoded!.buffer.asByteData());
1614+
// [encoded] wraps a read-only SkData buffer, so we wrap it here in
1615+
// an [UnmodifiableByteDataView].
1616+
callback(UnmodifiableByteDataView(encoded!.buffer.asByteData()));
16141617
});
16151618
});
16161619
}

lib/ui/painting/image_encoding.cc

Lines changed: 19 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,15 @@ 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+
// SkData are generally read-only.
57+
void* bytes = const_cast<void*>(buffer->data());
58+
const intptr_t length = buffer->size();
59+
void* peer = reinterpret_cast<void*>(buffer.release());
60+
Dart_Handle dart_data = Dart_NewExternalTypedDataWithFinalizer(
61+
Dart_TypedData_kUint8, bytes, length, peer, length, FinalizeSkData);
62+
DartInvoke(callback->value(), {dart_data});
5263
}
5364

5465
sk_sp<SkImage> ConvertToRasterUsingResourceContext(
@@ -222,9 +233,10 @@ void EncodeImageAndInvokeDataCallback(
222233
auto encode_task = [callback_task = std::move(callback_task), format,
223234
ui_task_runner](sk_sp<SkImage> raster_image) {
224235
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); });
236+
ui_task_runner->PostTask([callback_task = std::move(callback_task),
237+
encoded = std::move(encoded)]() mutable {
238+
callback_task(std::move(encoded));
239+
});
228240
};
229241

230242
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)