diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 2a33b976926b7..c4e29c1d5e8e5 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -2053,6 +2053,48 @@ Future 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 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 fromAsset(String assetKey) { + final ImmutableBuffer instance = ImmutableBuffer._(0); + return _futurize((_Callback callback) { + return instance._initFromAsset(assetKey, callback); + }).then((int length) => instance.._length = length); + } + void _init(Uint8List list, _Callback callback) native 'ImmutableBuffer_init'; + String? _initFromAsset(String assetKey, _Callback callback) native 'ImmutableBuffer_initFromAsset'; + /// The length, in bytes, of the underlying data. - final int length; + int get length => _length; + int _length; bool _debugDisposed = false; diff --git a/lib/ui/painting/immutable_buffer.cc b/lib/ui/painting/immutable_buffer.cc index e4d3f630de72f..8c9ddc9dedb15 100644 --- a/lib/ui/painting/immutable_buffer.cc +++ b/lib/ui/painting/immutable_buffer.cc @@ -7,6 +7,7 @@ #include #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(chars), + static_cast(asset_length)}; + + std::shared_ptr asset_manager = UIDartState::Current() + ->platform_configuration() + ->client() + ->GetAssetManager(); + std::unique_ptr data = asset_manager->GetAsMapping(asset_name); + if (data == nullptr) { + Dart_SetReturnValue(args, tonic::ToDart("Asset not found")); + return; + } + + auto size = data->GetSize(); + const void* bytes = static_cast(data->GetMapping()); + auto sk_data = MakeSkDataWithCopy(bytes, size); + auto buffer = fml::MakeRefCounted(sk_data); + buffer->AssociateWithDartWrapper(immutable_buffer); + tonic::DartInvoke(callback_handle, {tonic::ToDart(size)}); +} + size_t ImmutableBuffer::GetAllocationSize() const { return sizeof(ImmutableBuffer) + data_->size(); } diff --git a/lib/ui/painting/immutable_buffer.h b/lib/ui/painting/immutable_buffer.h index 6306dd99f0a73..1dd94a5ee920e 100644 --- a/lib/ui/painting/immutable_buffer.h +++ b/lib/ui/painting/immutable_buffer.h @@ -39,6 +39,19 @@ class ImmutableBuffer : public RefCountedDartWrappable { /// when the copy has completed. static void init(Dart_NativeArguments args); + /// Initializes a new ImmutableData from an asset matching a provided + /// asset string. + /// + /// The zero indexed argument is the caller that will be registered as the + /// Dart peer of the native ImmutableBuffer object. + /// + /// The first indexed argumented is a String corresponding to the asset + /// to load. + /// + /// The second indexed argument is expected to be a void callback to signal + /// when the copy has completed. + static void initFromAsset(Dart_NativeArguments args); + /// The length of the data in bytes. size_t length() const { FML_DCHECK(data_); diff --git a/lib/ui/window/platform_configuration.h b/lib/ui/window/platform_configuration.h index a8f3cf4ab3ac8..fb2dd7f6cd71f 100644 --- a/lib/ui/window/platform_configuration.h +++ b/lib/ui/window/platform_configuration.h @@ -11,6 +11,7 @@ #include #include +#include "flutter/assets/asset_manager.h" #include "flutter/fml/time/time_point.h" #include "flutter/lib/ui/semantics/semantics_update.h" #include "flutter/lib/ui/window/pointer_data_packet.h" @@ -98,6 +99,11 @@ class PlatformConfigurationClient { /// creation. virtual FontCollection& GetFontCollection() = 0; + //-------------------------------------------------------------------------- + /// @brief Returns the current collection of assets available on the + /// platform. + virtual std::shared_ptr GetAssetManager() = 0; + //-------------------------------------------------------------------------- /// @brief Notifies this client of the name of the root isolate and its /// port when that isolate is launched, restarted (in the diff --git a/lib/web_ui/lib/painting.dart b/lib/web_ui/lib/painting.dart index 09c249115e193..ede65f8ddb591 100644 --- a/lib/web_ui/lib/painting.dart +++ b/lib/web_ui/lib/painting.dart @@ -484,6 +484,20 @@ Future instantiateImageCodec( } } +Future instantiateImageCodecFromBuffer( + ImmutableBuffer buffer, { + int? targetWidth, + int? targetHeight, + bool allowUpscaling = true, +}) async { + if (engine.useCanvasKit) { + return engine.skiaInstantiateImageCodec(buffer._list!, targetWidth, targetHeight); + } else { + final html.Blob blob = html.Blob([buffer._list!.buffer]); + return engine.HtmlBlobCodec(blob); + } +} + Future webOnlyInstantiateImageCodecFromUrl(Uri uri, {engine.WebOnlyImageCodecChunkCallback? chunkCallback}) { if (engine.useCanvasKit) { @@ -735,15 +749,21 @@ class ImageShader extends Shader { } class ImmutableBuffer { - ImmutableBuffer._(this.length); + ImmutableBuffer._(this._length); static Future fromUint8List(Uint8List list) async { final ImmutableBuffer instance = ImmutableBuffer._(list.length); instance._list = list; return instance; } + static Future fromAsset(String assetKey) async { + throw UnsupportedError('ImmutableBuffer.fromAsset is not supported on the web.'); + } + Uint8List? _list; - final int length; + + int get length => _length; + int _length; bool get debugDisposed { late bool disposed; diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index e82f3391a5526..c865be32b2fcf 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -299,6 +299,11 @@ FontCollection& RuntimeController::GetFontCollection() { return client_.GetFontCollection(); } +// |PlatfromConfigurationClient| +std::shared_ptr RuntimeController::GetAssetManager() { + return client_.GetAssetManager(); +} + // |PlatformConfigurationClient| void RuntimeController::UpdateIsolateDescription(const std::string isolate_name, int64_t isolate_port) { diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index 6f476cbe3dbf0..bcb9b4a2e85f5 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -8,6 +8,7 @@ #include #include +#include "flutter/assets/asset_manager.h" #include "flutter/common/task_runners.h" #include "flutter/flow/layers/layer_tree.h" #include "flutter/fml/macros.h" @@ -614,6 +615,9 @@ class RuntimeController : public PlatformConfigurationClient { // |PlatformConfigurationClient| FontCollection& GetFontCollection() override; + // |PlatformConfigurationClient| + std::shared_ptr GetAssetManager() override; + // |PlatformConfigurationClient| void UpdateIsolateDescription(const std::string isolate_name, int64_t isolate_port) override; diff --git a/runtime/runtime_delegate.h b/runtime/runtime_delegate.h index ed57722e532f9..540e47dd09ba4 100644 --- a/runtime/runtime_delegate.h +++ b/runtime/runtime_delegate.h @@ -8,6 +8,7 @@ #include #include +#include "flutter/assets/asset_manager.h" #include "flutter/flow/layers/layer_tree.h" #include "flutter/lib/ui/semantics/custom_accessibility_action.h" #include "flutter/lib/ui/semantics/semantics_node.h" @@ -33,6 +34,8 @@ class RuntimeDelegate { virtual FontCollection& GetFontCollection() = 0; + virtual std::shared_ptr GetAssetManager() = 0; + virtual void OnRootIsolateCreated() = 0; virtual void UpdateIsolateDescription(const std::string isolate_name, diff --git a/shell/common/engine.h b/shell/common/engine.h index 206ecd2c37518..28762dab3ed69 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -755,8 +755,8 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { // |RuntimeDelegate| FontCollection& GetFontCollection() override; - // Return the asset manager associated with the current engine, or nullptr. - std::shared_ptr GetAssetManager(); + // |RuntimeDelegate| + std::shared_ptr GetAssetManager() override; // Return the weak_ptr of ImageDecoder. fml::WeakPtr GetImageDecoderWeakPtr(); diff --git a/shell/common/engine_unittests.cc b/shell/common/engine_unittests.cc index 4305633b53c53..ce6d9b2a0e956 100644 --- a/shell/common/engine_unittests.cc +++ b/shell/common/engine_unittests.cc @@ -53,6 +53,7 @@ class MockRuntimeDelegate : public RuntimeDelegate { void(SemanticsNodeUpdates, CustomAccessibilityActionUpdates)); MOCK_METHOD1(HandlePlatformMessage, void(std::unique_ptr)); MOCK_METHOD0(GetFontCollection, FontCollection&()); + MOCK_METHOD0(GetAssetManager, std::shared_ptr()); MOCK_METHOD0(OnRootIsolateCreated, void()); MOCK_METHOD2(UpdateIsolateDescription, void(const std::string, int64_t)); MOCK_METHOD1(SetNeedsReportTimings, void(bool)); diff --git a/testing/dart/BUILD.gn b/testing/dart/BUILD.gn index 08355bd26c1a0..da0730d6f7028 100644 --- a/testing/dart/BUILD.gn +++ b/testing/dart/BUILD.gn @@ -5,6 +5,7 @@ import("//flutter/testing/dart/compile_test.gni") tests = [ + "assets_test.dart", "canvas_test.dart", "channel_buffers_test.dart", "codec_test.dart", diff --git a/testing/dart/assets_test.dart b/testing/dart/assets_test.dart new file mode 100644 index 0000000000000..de975f471695e --- /dev/null +++ b/testing/dart/assets_test.dart @@ -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(); + }); +} diff --git a/testing/run_tests.py b/testing/run_tests.py index e0f7f3366b9d6..1826c397a3fc1 100755 --- a/testing/run_tests.py +++ b/testing/run_tests.py @@ -286,6 +286,7 @@ def RunDartTest(build_dir, test_packages, dart_file, verbose_dart_snapshot, mult command_args += [ '--use-test-fonts', '--icu-data-file-path=%s' % os.path.join(build_dir, 'icudtl.dat'), + '--flutter-assets-dir=%s' % os.path.join(build_dir, 'gen', 'flutter', 'lib', 'ui'), kernel_file_output, ]