-
Notifications
You must be signed in to change notification settings - Fork 6k
Add support for loading asset directly from ImmutableBuffer #32999
Changes from all commits
9623154
ba639ba
fe93694
7726151
9f45888
6963e65
5e2f694
769a9d8
68f5b9b
f216c48
bae7201
61ec3a3
614a997
6bb5767
8d31015
bcea89a
4fdfc74
83b9107
784254f
4f30721
bf0c614
a13380b
77b4eac
940d8d1
56029fd
512a2b6
d27b4eb
75b86f9
7082bdb
3c8a0e3
66ccfc5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2053,6 +2053,48 @@ Future<Codec> instantiateImageCodec( | |
bool allowUpscaling = true, | ||
}) async { | ||
final ImmutableBuffer buffer = await ImmutableBuffer.fromUint8List(list); | ||
return instantiateImageCodecFromBuffer( | ||
buffer, | ||
targetWidth: targetWidth, | ||
targetHeight: targetHeight, | ||
allowUpscaling: allowUpscaling, | ||
); | ||
} | ||
|
||
/// Instantiates an image [Codec]. | ||
/// | ||
/// This method is a convenience wrapper around the [ImageDescriptor] API, and | ||
/// using [ImageDescriptor] directly is preferred since it allows the caller to | ||
/// make better determinations about how and whether to use the `targetWidth` | ||
/// and `targetHeight` parameters. | ||
/// | ||
/// The [buffer] parameter is the binary image data (e.g a PNG or GIF binary data). | ||
/// The data can be for either static or animated images. The following image | ||
/// formats are supported: {@macro dart.ui.imageFormats} | ||
/// | ||
/// The [targetWidth] and [targetHeight] arguments specify the size of the | ||
/// output image, in image pixels. If they are not equal to the intrinsic | ||
/// dimensions of the image, then the image will be scaled after being decoded. | ||
/// If the `allowUpscaling` parameter is not set to true, both dimensions will | ||
/// be capped at the intrinsic dimensions of the image, even if only one of | ||
/// them would have exceeded those intrinsic dimensions. If exactly one of these | ||
/// two arguments is specified, then the aspect ratio will be maintained while | ||
/// forcing the image to match the other given dimension. If neither is | ||
/// specified, then the image maintains its intrinsic size. | ||
/// | ||
/// Scaling the image to larger than its intrinsic size should usually be | ||
/// avoided, since it causes the image to use more memory than necessary. | ||
/// Instead, prefer scaling the [Canvas] transform. If the image must be scaled | ||
/// up, the `allowUpscaling` parameter must be set to true. | ||
/// | ||
/// The returned future can complete with an error if the image decoding has | ||
/// failed. | ||
Future<Codec> instantiateImageCodecFromBuffer( | ||
ImmutableBuffer buffer, { | ||
int? targetWidth, | ||
int? targetHeight, | ||
bool allowUpscaling = true, | ||
}) async { | ||
final ImageDescriptor descriptor = await ImageDescriptor.encoded(buffer); | ||
if (!allowUpscaling) { | ||
if (targetWidth != null && targetWidth > descriptor.width) { | ||
|
@@ -5511,7 +5553,7 @@ class Shadow { | |
/// The creator of this object is responsible for calling [dispose] when it is | ||
/// no longer needed. | ||
class ImmutableBuffer extends NativeFieldWrapperClass1 { | ||
ImmutableBuffer._(this.length); | ||
ImmutableBuffer._(this._length); | ||
|
||
/// Creates a copy of the data from a [Uint8List] suitable for internal use | ||
/// in the engine. | ||
|
@@ -5521,10 +5563,24 @@ class ImmutableBuffer extends NativeFieldWrapperClass1 { | |
instance._init(list, callback); | ||
}).then((_) => instance); | ||
} | ||
|
||
/// Create a buffer from the asset with key [assetKey]. | ||
/// | ||
/// Throws an [Exception] if the asset does not exist. | ||
static Future<ImmutableBuffer> fromAsset(String assetKey) { | ||
final ImmutableBuffer instance = ImmutableBuffer._(0); | ||
return _futurize((_Callback<int> callback) { | ||
return instance._initFromAsset(assetKey, callback); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. _futurize handles throwing an exception if this method returns a non-null string. |
||
}).then((int length) => instance.._length = length); | ||
} | ||
|
||
void _init(Uint8List list, _Callback<void> callback) native 'ImmutableBuffer_init'; | ||
|
||
String? _initFromAsset(String assetKey, _Callback<int> callback) native 'ImmutableBuffer_initFromAsset'; | ||
|
||
/// The length, in bytes, of the underlying data. | ||
final int length; | ||
int get length => _length; | ||
int _length; | ||
|
||
bool _debugDisposed = false; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
#include <cstring> | ||
|
||
#include "flutter/lib/ui/ui_dart_state.h" | ||
#include "flutter/lib/ui/window/platform_configuration.h" | ||
#include "third_party/tonic/converter/dart_converter.h" | ||
#include "third_party/tonic/dart_args.h" | ||
#include "third_party/tonic/dart_binding_macros.h" | ||
|
@@ -30,6 +31,9 @@ ImmutableBuffer::~ImmutableBuffer() {} | |
void ImmutableBuffer::RegisterNatives(tonic::DartLibraryNatives* natives) { | ||
natives->Register({{"ImmutableBuffer_init", ImmutableBuffer::init, 3, true}, | ||
FOR_EACH_BINDING(DART_REGISTER_NATIVE)}); | ||
natives->Register({{"ImmutableBuffer_initFromAsset", | ||
ImmutableBuffer::initFromAsset, 3, true}, | ||
FOR_EACH_BINDING(DART_REGISTER_NATIVE)}); | ||
} | ||
|
||
void ImmutableBuffer::init(Dart_NativeArguments args) { | ||
|
@@ -49,6 +53,45 @@ void ImmutableBuffer::init(Dart_NativeArguments args) { | |
tonic::DartInvoke(callback_handle, {Dart_TypeVoid()}); | ||
} | ||
|
||
void ImmutableBuffer::initFromAsset(Dart_NativeArguments args) { | ||
UIDartState::ThrowIfUIOperationsProhibited(); | ||
Dart_Handle callback_handle = Dart_GetNativeArgument(args, 2); | ||
if (!Dart_IsClosure(callback_handle)) { | ||
Dart_SetReturnValue(args, tonic::ToDart("Callback must be a function")); | ||
return; | ||
} | ||
Dart_Handle asset_name_handle = Dart_GetNativeArgument(args, 1); | ||
uint8_t* chars = nullptr; | ||
intptr_t asset_length = 0; | ||
Dart_Handle result = | ||
Dart_StringToUTF8(asset_name_handle, &chars, &asset_length); | ||
if (Dart_IsError(result)) { | ||
Dart_SetReturnValue(args, tonic::ToDart("Asset must be valid UTF8")); | ||
return; | ||
} | ||
Dart_Handle immutable_buffer = Dart_GetNativeArgument(args, 0); | ||
|
||
std::string asset_name = std::string{reinterpret_cast<const char*>(chars), | ||
static_cast<size_t>(asset_length)}; | ||
|
||
std::shared_ptr<AssetManager> asset_manager = UIDartState::Current() | ||
->platform_configuration() | ||
->client() | ||
->GetAssetManager(); | ||
std::unique_ptr<fml::Mapping> data = asset_manager->GetAsMapping(asset_name); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these mappings thread safe? These usually come from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm. Sounds like we can't avoid the copy in that case. If we didn't expose the lenght, we could get away with this being something that's done entirely on the decoder thread though right? We could just give that a way to acquire the data and then let it do the whole thing there. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need the length in the framework for this case. We could always say "length might be -1 for certain instances not created from a uint8list" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we could also copy once into an skdata - that would still reduce the number of copies by 1. Though having a single createCodecFromAsset method would mean we could do the entire thing on the right thread, i'm not sure how well that works with the framework wanting to abstract byte acquisition from image decoding. I don't think adding a special case for asset images would be a bad thing fwiw, but it would be a special case. |
||
if (data == nullptr) { | ||
Dart_SetReturnValue(args, tonic::ToDart("Asset not found")); | ||
return; | ||
} | ||
|
||
auto size = data->GetSize(); | ||
const void* bytes = static_cast<const void*>(data->GetMapping()); | ||
auto sk_data = MakeSkDataWithCopy(bytes, size); | ||
auto buffer = fml::MakeRefCounted<ImmutableBuffer>(sk_data); | ||
buffer->AssociateWithDartWrapper(immutable_buffer); | ||
tonic::DartInvoke(callback_handle, {tonic::ToDart(size)}); | ||
} | ||
|
||
size_t ImmutableBuffer::GetAllocationSize() const { | ||
return sizeof(ImmutableBuffer) + data_->size(); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// 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' as ui; | ||
|
||
import 'package:litetest/litetest.dart'; | ||
|
||
void main() { | ||
test('Loading an asset that does not exist returns null', () async { | ||
Object? error; | ||
try { | ||
await ui.ImmutableBuffer.fromAsset('ThisDoesNotExist'); | ||
} catch (err) { | ||
error = err; | ||
} | ||
expect(error, isNotNull); | ||
expect(error is Exception, true); | ||
}); | ||
|
||
test('returns the bytes of a bundled asset', () async { | ||
final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromAsset('assets/DashInNooglerHat.jpg'); | ||
|
||
expect(buffer.length == 354679, true); | ||
}); | ||
|
||
test('can dispose immutable buffer', () async { | ||
final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromAsset('assets/DashInNooglerHat.jpg'); | ||
|
||
buffer.dispose(); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't think of what to do besides copy the API