Repository navigation
Refactor instantiateImageCodec/decodeImageFromPixels to provide an ImageDescriptor - #19537
Conversation
| final PixelFormat? _format; | ||
|
|
||
| /// The width, in pixels, of the image. | ||
| int get width => _width != null ? _width : throw UnsupportedError('ImageDescriptor.width is not supported on web.'); |
There was a problem hiding this comment.
@jonahwilliams - is there some way we can implement these? Should I just be filing a bug?
There was a problem hiding this comment.
There is no way these can be implemented on the html backend as far as I know. Fyi @yjbanov
For the canvaskit backend, it might be feasible? @hterkelsen
| } | ||
| } | ||
| return descriptor.instantiateCodec( | ||
| targetWidth: targetWidth ?? _kDoNotResizeDimension, |
There was a problem hiding this comment.
IMO, this API can be made even simpler if the engine mandates that the target width and height can never be unspecified. So, instead of _kDoNotResizeDimension, return descriptor.width or height since we now have this information in Dart code already. You can then remove _kDoNotResizeDimension and all the places where the engine has to deal with optionals.
There was a problem hiding this comment.
Ditto elsewhere on all uses of _kDoNotResizeDimension.
There was a problem hiding this comment.
To be clear - this means we always check for GetResizedDimension, and that we'd want to remove the std::optionalness on the C++ side?
There was a problem hiding this comment.
Right. There is already a fast path where the resized dimensions are the same as the intrinsic dimensions.
There was a problem hiding this comment.
Ok. This turned out to be a bit of a hassle but I think it's better this way. The aspect ratio code that used to live in C++ now lives in Dart.
| final int length; | ||
|
|
||
| /// Retreives a single byte from the underlying data. | ||
| int operator [](int index) => elementAt(index); |
There was a problem hiding this comment.
Is there a current use case for this? The caller can already work on the source buffer before creating the immutable variant. Keeping it super simple, I would just make this an opaque handle for now. We can add to it as the use cases arise. The engine can get more creative with where it puts the buffer if it doesn't have to worry about also providing synchronous access pixel data.
There was a problem hiding this comment.
No, I can remove this.
|
|
||
| /// Creates a [Codec] object which is suitable for decoding the data in the | ||
| /// buffer to an [Image]. | ||
| Future<Codec> instantiateCodec({int? targetWidth, int? targetHeight, bool disposeBuffer = true}) { |
There was a problem hiding this comment.
Please document disposeBuffer and its semantics.
There was a problem hiding this comment.
Also, it's not used so maybe patch that too :)
There was a problem hiding this comment.
That was meant to be removed - it's been removed in the latest commit.
There was a problem hiding this comment.
Sound good. I think I submitted the review after you amended the patch and I was confused where the comment thread went.
| /// pixels in the data buffer. If unspecified, it defaults to `width` multiplied | ||
| /// by the number of bytes per pixel in the provided `format`. | ||
| // Not async because there's no expensive work to do here. | ||
| ImageDescriptor.raw( |
There was a problem hiding this comment.
Minor nit: Is it conventional for "raw" to mean from decompressed image data. I always found that terminology to be a bit terse. How about "imageData"? Either way is fine. Whatever you think is clearer.
There was a problem hiding this comment.
How about decompressed?
There was a problem hiding this comment.
I like pixels. But really, it was a just a minor observation. Fine either way.
|
|
||
| namespace flutter { | ||
|
|
||
| class ImageDescriptor : public RefCountedDartWrappable<ImageDescriptor> { |
There was a problem hiding this comment.
Header docs here please. Specifically, descriptors on other platforms/APIs are extremely lightweight objects. They usually they just "describe" a thing but don't own it necessarily. This is not the case with this implementation. I'd make that clear upfront. Other stuff is mostly self explanatory.
There was a problem hiding this comment.
On that note, maybe there is a better a better name for this class since it owns the underlying allocation as well as describing it. ImageCodec maybe?
There was a problem hiding this comment.
I think that's confusing given we already have Codec and SingleFrameCodec/MultiFrameCodec. It's also a bit confusing to have ImageCodec::instantiateCodec.
I can add the docs. I can't think of a good way to make this object more light weight than it is, or to avoid retaining the underlying data for it.
| } | ||
|
|
||
| size_t GetAllocationSize() const override { | ||
| return sizeof(ImmutableBuffer) + (data_->size() * 4); |
There was a problem hiding this comment.
The * 4 is inaccurate. The data size is already in bytes.
| return data_->size(); | ||
| } | ||
|
|
||
| int elementAt(size_t index) const { |
There was a problem hiding this comment.
See my comment about this being unnecessary.
| #include "third_party/tonic/typed_data/typed_list.h" | ||
|
|
||
| namespace flutter { | ||
| class ImmutableBuffer : public RefCountedDartWrappable<ImmutableBuffer> { |
| namespace flutter { | ||
| class ImmutableBuffer : public RefCountedDartWrappable<ImmutableBuffer> { | ||
| public: | ||
| ~ImmutableBuffer() override = default; |
There was a problem hiding this comment.
OOL this please. This applies to all virtual methods elsewhere as well.
|
|
||
| static void init(Dart_NativeArguments args); | ||
|
|
||
| int length() const { |
There was a problem hiding this comment.
size_t length() const;
| void _initRaw(ImageDescriptor outDescriptor, ImmutableBuffer buffer, int width, int height, int rowBytes, int pixelFormat) native 'ImageDescriptor_initRaw'; | ||
|
|
||
| /// The width, in pixels, of the image. | ||
| int get width native 'ImageDescriptor_width'; |
There was a problem hiding this comment.
Can width, height, or bytesPerPixel change? If not, then you can cache them in Dart fields, which will have faster access than a runtime call.
int? _width;
int get width => _width ??= _getWidth();
int _getWidth() native 'ImageDescriptor_width';There was a problem hiding this comment.
These are immutable - we can cache them.
Do we have any benchmarks demonstrating this is faster though? At one point I thought I measured this and it didn't make a big difference.
There was a problem hiding this comment.
I doubt we have any benchmarks doing an A/B comparison, but in general the difference is a null check + load (3-4 instructions) vs. saving live registers, doing the runtime call, looking up the native call by string, setting up handle and allocation arenas in the runtime, etc., etc.., and then popping back out undoing all that. E.g.:
https://github.com/dart-lang/sdk/blob/master/runtime/vm/compiler/assembler/assembler_arm64.cc#L1488,
https://github.com/dart-lang/sdk/blob/master/runtime/vm/compiler/stub_code_compiler_arm64.cc#L74
and so on.
| } | ||
| return _elementAt(index); | ||
| } | ||
| int _elementAt(int index) native 'ImmutableBuffer_elementAt'; |
There was a problem hiding this comment.
A runtime call per byte access will be very inefficient. If this were staying here, I'd recommend caching a Uint8List in a field of this object that is backed by an external typed data.
There was a problem hiding this comment.
Per the comment thread above, this is being removed completely as there is no use case for it.
|
@zanderso @chinmaygarde I believe all feedback has been addressed, and tests are passing locally (we'll see if CI agrees). PTAL. |
|
Presubmit check failures look related. |
|
Yeah, the include was too early (OS_ANDROID wasn't defined yet), and the web had some merge issues to fix for NNBD. Newest commit should build .. crosses fingers |
| SkISize::Make(600, 200)); | ||
| } | ||
|
|
||
| TEST(ImageDecoderTest, VerifySimpleDecodingNoUpscalingOneDimension) { |
There was a problem hiding this comment.
This functionality has been moved over to the dart side and is tested in resize_image_test.dart
jonahwilliams
left a comment
There was a problem hiding this comment.
I would clearly indicate on the dart:ui dartdocs which APIs (if any) are supported on the web
|
@jonahwilliams - are we doing that for other APIs? These seem like they should be possible to implement on the Skia and the DOM backends... /cc @ferhatb @yjbanov @hterkelsen |
|
Mac iOS engine was a flake, rerun here: https://ci.chromium.org/p/flutter/builders/try/Mac%20iOS%20Engine/5393 |
Not always, but we should :) |
|
in other words, it would generally be good to let users know ahead of time that an API isn't implemented, rather than have them fail to use it or misunderstand the error message |
|
Added those docs @jonahwilliams |
|
Updated this to make use of new Skia API from https://skia.googlesource.com/skia/+/d9eb219e809ac2e282f36c1e99a52cc1ae7522a8 |
|
Once https://skia-review.googlesource.com/c/skia/+/302270 rolls in I can clean up my latest patch a little further to get rid of some of the variant weirdness and just have everything go through SkCodecImageGenerator. |
| int _getWidth() native 'ImageDescriptor_width'; | ||
| /// The width, in pixels, of the image. | ||
| /// | ||
| /// On web, this is only supported for [raw] images. |
| /// | ||
| /// Use this class to determine the height, width, and byte size of image data | ||
| /// before decoding it. | ||
| class ImageDescriptor extends NativeFieldWrapperClass2 { |
There was a problem hiding this comment.
Is the a web_ui implementation of this Type?
There was a problem hiding this comment.
jonahwilliams
left a comment
There was a problem hiding this comment.
All of my comments have been addressed, LGTM (if LG to others...)
…de an ImageDescriptor (flutter-team-archive/engine#19537)
…de an ImageDescriptor (flutter-team-archive/engine#19537)
Description
Refactors instantiateImageCodec and decodeImageFromPixels to provide intermediate data about the image width, height, and bytes per pixel. This allows for more fine grained control from Dart, particularly when trying to reason about how or whether to maintain aspect ratio in the targetWidth/targetHeight parameters.
This leaves the existing methods functional by re-implementing them to use the new ImmutableBuffer/ImageDescriptor class.
The ImmutableBuffer class is provided so that callers can avoid copying the image data multiple times if they wish to re-recreate the image descriptor.
Related Issues
#19385
flutter/flutter#59578
Tests
I added the following tests:
Tests to exercise the new classes/behaviors.
Breaking Change
Did any tests fail when you ran them? Please read handling breaking changes.