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

Refactor instantiateImageCodec/decodeImageFromPixels to provide an ImageDescriptor - #19537

Merged
dnfield merged 22 commits into
flutter-team-archive:masterfrom
dnfield:image_descriptor
Jul 21, 2020
Merged

dnfield merged 22 commits into
flutter-team-archive:masterfrom
dnfield:image_descriptor

Conversation

@dnfield

@dnfield dnfield commented Jul 6, 2020

Copy link
Copy Markdown
Contributor

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.

  • No, no existing tests failed, so this is not a breaking change.

@auto-assign
auto-assign Bot requested a review from liyuqian July 6, 2020 18:50
@dnfield
dnfield requested review from chinmaygarde and zanderso July 6, 2020 19:28
@dnfield
dnfield requested a review from jonahwilliams July 6, 2020 19:55
Comment thread lib/web_ui/lib/src/ui/painting.dart Outdated
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.');

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jonahwilliams - is there some way we can implement these? Should I just be filing a bug?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread lib/ui/painting.dart Outdated
}
}
return descriptor.instantiateCodec(
targetWidth: targetWidth ?? _kDoNotResizeDimension,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto elsewhere on all uses of _kDoNotResizeDimension.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear - this means we always check for GetResizedDimension, and that we'd want to remove the std::optionalness on the C++ side?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. There is already a fast path where the resized dimensions are the same as the intrinsic dimensions.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread lib/ui/painting.dart Outdated
final int length;

/// Retreives a single byte from the underlying data.
int operator [](int index) => elementAt(index);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I can remove this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

Comment thread lib/ui/painting.dart Outdated

/// 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}) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please document disposeBuffer and its semantics.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, it's not used so maybe patch that too :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was meant to be removed - it's been removed in the latest commit.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sound good. I think I submitted the review after you amended the patch and I was confused where the comment thread went.

Comment thread lib/ui/painting.dart
/// 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(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about decompressed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or just pixels?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like pixels. But really, it was a just a minor observation. Fine either way.


namespace flutter {

class ImageDescriptor : public RefCountedDartWrappable<ImageDescriptor> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread lib/ui/painting/immutable_buffer.h Outdated
}

size_t GetAllocationSize() const override {
return sizeof(ImmutableBuffer) + (data_->size() * 4);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The * 4 is inaccurate. The data size is already in bytes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread lib/ui/painting/immutable_buffer.h Outdated
return data_->size();
}

int elementAt(size_t index) const {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment about this being unnecessary.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

#include "third_party/tonic/typed_data/typed_list.h"

namespace flutter {
class ImmutableBuffer : public RefCountedDartWrappable<ImmutableBuffer> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Header docs.

Comment thread lib/ui/painting/immutable_buffer.h Outdated
namespace flutter {
class ImmutableBuffer : public RefCountedDartWrappable<ImmutableBuffer> {
public:
~ImmutableBuffer() override = default;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OOL this please. This applies to all virtual methods elsewhere as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread lib/ui/painting/immutable_buffer.h Outdated

static void init(Dart_NativeArguments args);

int length() const {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

size_t length() const;

Comment thread lib/ui/painting.dart Outdated
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';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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';

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either way, done.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread lib/ui/painting.dart Outdated
}
return _elementAt(index);
}
int _elementAt(int index) native 'ImmutableBuffer_elementAt';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per the comment thread above, this is being removed completely as there is no use case for it.

@dnfield

dnfield commented Jul 9, 2020

Copy link
Copy Markdown
Contributor Author

@zanderso @chinmaygarde I believe all feedback has been addressed, and tests are passing locally (we'll see if CI agrees). PTAL.

@zanderso

zanderso commented Jul 9, 2020

Copy link
Copy Markdown
Contributor

Presubmit check failures look related.

@dnfield

dnfield commented Jul 9, 2020

Copy link
Copy Markdown
Contributor Author

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This functionality has been moved over to the dart side and is tested in resize_image_test.dart

@jonahwilliams jonahwilliams left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would clearly indicate on the dart:ui dartdocs which APIs (if any) are supported on the web

@dnfield

dnfield commented Jul 9, 2020

Copy link
Copy Markdown
Contributor Author

@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

@dnfield

dnfield commented Jul 9, 2020

Copy link
Copy Markdown
Contributor Author

@jonahwilliams

Copy link
Copy Markdown
Contributor

@jonahwilliams - are we doing that for other APIs?

Not always, but we should :)

@jonahwilliams

Copy link
Copy Markdown
Contributor

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

@dnfield

dnfield commented Jul 10, 2020

Copy link
Copy Markdown
Contributor Author

Added those docs @jonahwilliams

@dnfield

dnfield commented Jul 11, 2020

Copy link
Copy Markdown
Contributor Author

@dnfield

dnfield commented Jul 13, 2020

Copy link
Copy Markdown
Contributor Author

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.

Comment thread lib/ui/painting.dart Outdated
int _getWidth() native 'ImageDescriptor_width';
/// The width, in pixels, of the image.
///
/// On web, this is only supported for [raw] images.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uber-nit: "the web"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread lib/ui/painting.dart
///
/// Use this class to determine the height, width, and byte size of image data
/// before decoding it.
class ImageDescriptor extends NativeFieldWrapperClass2 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the a web_ui implementation of this Type?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jonahwilliams jonahwilliams left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of my comments have been addressed, LGTM (if LG to others...)

@zanderso
zanderso requested a review from chinmaygarde July 14, 2020 19:48
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Development

Successfully merging this pull request may close these issues.

5 participants