-
Notifications
You must be signed in to change notification settings - Fork 6k
Conversation
It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!). If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix? Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
@yjbanov I'm wondering how we can support this API in the web engine. Thoughts? |
lib/ui/painting.dart
Outdated
/// | ||
/// * [instantiateImageCodecWithSize], which used this signature for its | ||
/// `getTargetSize` argument. | ||
typedef TargetImageSizeProducer = TargetImageSize Function(ImageDescriptor descriptor); |
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.
I think we generally use "Callback" as the suffix for function types
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.
Done
lib/ui/painting.dart
Outdated
}) async { | ||
getTargetSize ??= (ImageDescriptor descriptor) => const TargetImageSize(); |
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.
Instead of using ??=
here, create a static function and make it the argument default value:
TargetImageSize _defaultGetTargetSize(ImageDescriptor descriptor) {
return const TargetImageSize();
}
TargetImageSizeProducer getTargetSize = _defaultGetTargetSize,
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.
Heh - I did that at first, but then the package:flutter/painting.dart
API wrapper has to do something like this:
Future<ui.Codec> instantiateImageCodecWithSize(
ui.ImmutableBuffer buffer, {
ui.TargetImageSizeProducer? getTargetSize,
}) {
if (getTargetSize == null) {
return ui.instantiateImageCodecWithSize(buffer);
} else {
return ui.instantiateImageCodecWithSize(buffer, getTargetSize: getTargetSize);
}
}
Either that, or the static function has to be public (or worse: copied). After discussing with Bob, he suggested that he frequently resorts to the method I've done here. What do you think given those tradeoffs?
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.
ahh yeah, that makes sense. I think the approach is fine. I would still use a static function instead of creating a new closure on each run though
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.
Done
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.
I think overall this approach LGTM. How does this look on the web side of things?
I added a web impl. It's not pretty.... definitely interested in knowing if there's a better way. |
lib/web_ui/lib/painting.dart
Outdated
final int height; | ||
|
||
@override | ||
dynamic noSuchMethod(Invocation invocation) => throw UnsupportedError('ImageDescriptor.${invocation.memberName} is not supported on web within a TargetImageSizeCallback.'); |
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.
Nooooooooo, can we avoid using noSuchMethod? :)
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.
Yep, this can really blow up the JS bundle size.
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.
TIL! I'll update it. Reason I went with this is because it was forcing me to implement all the private methods too, which was a bummer.
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.
Done - I got rid of the need for the ImageDescriptor at all.
Talked about this a bit offline with @chinmaygarde . I think we can already accomplish this with our existing API, but its not very obvious how to do so. For example, you can set one of targetWidth and targetHeight to a maximum value, but don't set the other one - this will preserve the aspect ratio. Then set allowUpscalling to false, this will prevent the image from being resized to be larger. Given that, I suspect you could probably implement this API in the framework without any dart:ui changes |
lib/ui/painting.dart
Outdated
/// must be positive. | ||
const TargetImageSize({this.width, this.height}) | ||
: assert(width == null || width > 0), | ||
assert(width == null || width > 0); |
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.
nit: I think you meant for this to be height
lib/web_ui/lib/painting.dart
Outdated
class TargetImageSize { | ||
const TargetImageSize({this.width, this.height}) | ||
: assert(width == null || width > 0), | ||
assert(width == null || width > 0); |
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.
nit: height
here too
/// Instead, prefer scaling the [Canvas] transform. | ||
/// | ||
/// The returned future can complete with an error if the image decoding has | ||
/// failed. |
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.
maybe note here that image resizing capabilities are only available with the canvaskit renderer and not html renderer (we should probably update the api docs for instantiateImageCodec
to note this too)
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.
Done
That only works if you at least know the aspect ratio of the image. If I want to size an image to fit within e.g. a square bounding box of 100x100, but I don't know if the image is portrait or landscape orientation, then I don't know whether to say "load the image with width=100 and let height be flexible" or "load the image at height=100 and let the width be flexible". This was the use case that drove this change. It comes about when you load a stream of random images over the network via a public API (e.g. Google Photos, Flickr, etc.). |
so in that case you'd have to say maxWidth = 100, maxHeight = 100. but then that doesn't preserve the aspect ratio? |
Correct - that stretches the image to a square. |
Note that the current API doesn't specify maxWidth & maxHeight -- it specifies targetWidth & targetHeight |
with allowUpscaling= false then targetSise becomes maxSize essentially |
Could we accomplish something similar with a preserveAspectRatio flag that behaved like you expected? |
No, allowUpscaling=false only ensures that you don't increase the target width beyond the intrinsic width (or the target height beyond the intrinsic height) Lines 2151 to 2158 in 7d40e77
|
We already do preserve the aspect ratio if you only specify one of the target width or height. But the problem is, you don't know which one to pass if you don't already know its aspect ratio. We could (and I considered) add a new parameter (or new signature) that had the caller pass a bounding box, and we'd fit the target image within the bounding box while maintaining the aspect ratio. But such an API change would be just as disruptive as the change in this PR, but less flexible. |
This adds a new `instantiateImageCodecWithSize` method, which can be used to decode an image into a size, where the target size isn't known until the caller is allowed to inspect the image descriptor. This enables the use case of loading an image whose aspect ratio isn't known at load time, and which needs to be resized to fit within a bounding box while also maintaining its original aspect ratio. flutter/flutter#118543
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.
Overall LGTM, and looks like all comments have been addressed. One nit about whether we could generalise TargetImageSize
to avoid adding more such classes later elsewhere.
|
||
typedef TargetImageSizeCallback = TargetImageSize Function(int intrinsicWidth, int intrinsicHeight); | ||
|
||
class TargetImageSize { |
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.
Are there other places in dart:ui we could use an int-based size? Might make sense to generalise the name so others could use it later, if so.
I can’t think of a great name though. Maybe something like PhysicalSize
.
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.
I thought about that, but a real PhysicalSize
class would want both width & height to be non-null. This is unique in how it allows one or both of those to be null.
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.
More similar to an IntConstraints type, not quite a size. We do really need an ISize type, but probably unrelated.
I think that this is a good feature, but I think it should actually be implemented in foundation instead of dart:ui. It does not require any new native bindings as far as I can tell. |
There's an optimization I've discussed with @yjbanov and @alanwutang11 that will need to be done in the web engine, so it's probably best left here. |
Which one? |
If the user passes a callback, but it returns a |
Interesting! Please file a bug/todo for tracking this |
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.
LGTM
…e#38905) (#118989) Commit: 696a84b1ed7da6d8edc69e334a9ec1f3d4ad478f
* 044e344 a8522271c Roll Fuchsia Mac SDK from 5TQ9IL4-Yu3KHCR-H... to R4F4q-h902yt4s7ow... (flutter/engine#39058) (flutter/flutter#118984) * b974eac b3da52d8c Roll Fuchsia Linux SDK from l3c_b-vRr-o6ZFX_M... to f613tOkDB282hW2tA... (flutter/engine#39061) (flutter/flutter#118987) * 696a84b 1e4e11ad1 Add more flexible image loading API (flutter/engine#38905) (flutter/flutter#118989) * 6ae7ad7 92313596d Roll Dart SDK from 548678dd684c to 608a0691a1d7 (1 revision) (flutter/engine#39063) (flutter/flutter#118990) * bd7bee0 Roll Flutter Engine from 92313596d77b to 8e7bc509e0d7 (3 revisions) (flutter/flutter#119004)
* 044e344 a8522271c Roll Fuchsia Mac SDK from 5TQ9IL4-Yu3KHCR-H... to R4F4q-h902yt4s7ow... (flutter/engine#39058) (flutter/flutter#118984) * b974eac b3da52d8c Roll Fuchsia Linux SDK from l3c_b-vRr-o6ZFX_M... to f613tOkDB282hW2tA... (flutter/engine#39061) (flutter/flutter#118987) * 696a84b 1e4e11ad1 Add more flexible image loading API (flutter/engine#38905) (flutter/flutter#118989) * 6ae7ad7 92313596d Roll Dart SDK from 548678dd684c to 608a0691a1d7 (1 revision) (flutter/engine#39063) (flutter/flutter#118990) * bd7bee0 Roll Flutter Engine from 92313596d77b to 8e7bc509e0d7 (3 revisions) (flutter/flutter#119004)
* Add more flexible image loading API This adds a new `instantiateImageCodecWithSize` method, which can be used to decode an image into a size, where the target size isn't known until the caller is allowed to inspect the image descriptor. This enables the use case of loading an image whose aspect ratio isn't known at load time, and which needs to be resized to fit within a bounding box while also maintaining its original aspect ratio. flutter/flutter#118543 * Add test * Fixed test failure * Update * Respond to review comments * Add web implementation * Fixed typo * Review comments Also changed the TargetImageSizeCallback to just take intrinsic width & height, rather than the full image descriptor. * Forgot to remove the _SizeOnlyImageDescriptor class - it's no longer needed * Forgot to update test
* 044e344 a8522271c Roll Fuchsia Mac SDK from 5TQ9IL4-Yu3KHCR-H... to R4F4q-h902yt4s7ow... (flutter/engine#39058) (flutter/flutter#118984) * b974eac b3da52d8c Roll Fuchsia Linux SDK from l3c_b-vRr-o6ZFX_M... to f613tOkDB282hW2tA... (flutter/engine#39061) (flutter/flutter#118987) * 696a84b 1e4e11ad1 Add more flexible image loading API (flutter/engine#38905) (flutter/flutter#118989) * 6ae7ad7 92313596d Roll Dart SDK from 548678dd684c to 608a0691a1d7 (1 revision) (flutter/engine#39063) (flutter/flutter#118990) * bd7bee0 Roll Flutter Engine from 92313596d77b to 8e7bc509e0d7 (3 revisions) (flutter/flutter#119004)
This adds a new
instantiateImageCodecWithSize
method, which can be used to decode an image into a size, where the target size isn't known until the caller is allowed to inspect the image descriptor.This enables the use case of loading an image whose aspect ratio isn't known at load time, and which needs to be resized to fit within a bounding box while also maintaining its original aspect ratio.
flutter/flutter#118543
Pre-launch Checklist
///
).