Add createImageFromTextureSource method to ui_web (flutter/engine#53483)

Adds createImageFromTextureSource for flutter web, exposed in dart:web_ui.

```dart
final ui.Image uiImage = renderer.createImageFromTextureSource(bitmap,
            width: 150, height: 150, transferOwnership: false);
```

In canvaskit renderer, this will use MakeLazyImageFromTextureSource. In SkWasmRenderer, it will call imageCreateFromTextureSource, which was already implemented in SkWasm for createImageFromImageBitmap to use, but was not exposed in a way that it could be taken advantage of.

By default, createImageFromTextureSource will create a copy of the object it is passed, but transferOwnership: true may be specified to allow transferable objects to be transferred to the renderer, avoiding the copy.

Fixes: https://github.com/flutter/flutter/issues/150479
Fixes: https://github.com/flutter/flutter/issues/144815
This commit is contained in:
jezell 2024-06-21 13:28:55 -07:00 committed by GitHub
parent b60c3db4a5
commit d1e6ca839c
7 changed files with 97 additions and 0 deletions

View File

@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:js_interop';
import 'dart:math' as math;
import 'dart:typed_data';
@ -238,6 +239,29 @@ class CanvasKitRenderer implements Renderer {
return CkImage(skImage);
}
@override
FutureOr<ui.Image> createImageFromTextureSource(JSAny object,
{required int width, required int height, required bool transferOwnership}) async {
if (!transferOwnership) {
final DomImageBitmap bitmap = await createImageBitmap(object, (x:0, y: 0, width: width, height: height));
return createImageFromImageBitmap(bitmap);
}
final SkImage? skImage = canvasKit.MakeLazyImageFromTextureSourceWithInfo(
object,
SkPartialImageInfo(
width: width.toDouble(),
height: height.toDouble(),
alphaType: canvasKit.AlphaType.Premul,
colorType: canvasKit.ColorType.RGBA_8888,
colorSpace: SkColorSpaceSRGB,
));
if (skImage == null) {
throw Exception('Failed to convert image bitmap to an SkImage.');
}
return CkImage(skImage);
}
@override
void decodeImageFromPixels(Uint8List pixels, int width, int height,
ui.PixelFormat format, ui.ImageDecoderCallback callback,

View File

@ -371,4 +371,9 @@ class HtmlRenderer implements Renderer {
imageElement.src = await canvas.toDataUrl();
return completer.future;
}
@override
FutureOr<ui.Image> createImageFromTextureSource(JSAny object, { required int width, required int height, required bool transferOwnership }) {
throw Exception('Not implemented for HTML renderer');
}
}

View File

@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:js_interop';
import 'dart:math' as math;
import 'dart:typed_data';
@ -129,6 +130,9 @@ abstract class Renderer {
FutureOr<ui.Image> createImageFromImageBitmap(DomImageBitmap imageSource);
FutureOr<ui.Image> createImageFromTextureSource(JSAny object,
{required int width, required int height, required bool transferOwnership});
void decodeImageFromPixels(
Uint8List pixels,
int width,

View File

@ -472,6 +472,19 @@ class SkwasmRenderer implements Renderer {
surface.handle,
));
}
@override
FutureOr<ui.Image> createImageFromTextureSource(JSAny textureSource, { required int width, required int height, required bool transferOwnership }) async {
if (!transferOwnership) {
textureSource = (await createImageBitmap(textureSource, (x: 0, y: 0, width: width, height: height))).toJSAnyShallow;
}
return SkwasmImage(imageCreateFromTextureSource(
textureSource as JSObject,
width,
height,
surface.handle,
));
}
}
class SkwasmPictureRenderer implements PictureRenderer {

View File

@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:js_interop';
import 'dart:math' as math;
import 'dart:typed_data';
@ -189,4 +190,9 @@ class SkwasmRenderer implements Renderer {
ui.Image createImageFromImageBitmap(DomImageBitmap imageSource) {
throw UnimplementedError('Skwasm not implemented on this platform.');
}
@override
ui.Image createImageFromTextureSource(JSAny object, { required int width, required int height, required bool transferOwnership }) {
throw Exception('Skwasm not implemented on this platform.');
}
}

View File

@ -44,3 +44,17 @@ FutureOr<ui.Image> createImageFromImageBitmap(JSAny imageSource) {
imageSource as DomImageBitmap,
);
}
/// Creates a [ui.Image] from a valid texture source (for example
/// HTMLImageElement | HTMLVideoElement | HTMLCanvasElement).
///
/// By default, [transferOwnership] specifies that the ownership of the texture
/// will be not be transferred to the renderer, and a copy of the texture source
/// will be made. If this is not desired, the ownership of the object can be
/// transferred to the renderer and the engine will take ownership of the
/// texture source and consume its contents.
FutureOr<ui.Image> createImageFromTextureSource(JSAny object, { required int width, required int height, bool transferOwnership = false }) {
return renderer.createImageFromTextureSource(
object, width: width, height: height, transferOwnership: transferOwnership
);
}

View File

@ -420,6 +420,37 @@ Future<void> testMain() async {
});
}
// This API doesn't work in headless Firefox due to requiring WebGL
// See https://github.com/flutter/flutter/issues/109265
if (!isFirefox && !isHtml) {
emitImageTests('svg_image_bitmap_texture_source', () async {
final DomBlob svgBlob = createDomBlob(<String>[
'''
<svg xmlns="http://www.w3.org/2000/svg" width="150" height="150">
<path d="M25,75 A50,50 0 1,0 125 75 L75,25 Z" stroke="blue" stroke-width="10" fill="red"></path>
</svg>
'''
], <String, String>{
'type': 'image/svg+xml'
});
final String url = domWindow.URL.createObjectURL(svgBlob);
final DomHTMLImageElement image = createDomHTMLImageElement();
final Completer<void> completer = Completer<void>();
late final DomEventListener loadListener;
loadListener = createDomEventListener((DomEvent event) {
completer.complete();
image.removeEventListener('load', loadListener);
});
image.addEventListener('load', loadListener);
image.src = url;
await completer.future;
final ui.Image uiImage =
await renderer.createImageFromTextureSource(image.toJSAnyShallow, width: 150, height: 150, transferOwnership: false);
return uiImage;
});
}
emitImageTests('codec_list_resized', () async {
final ByteBuffer data = await httpFetchByteBuffer('/test_images/mandrill_128.png');
final ui.Codec codec = await renderer.instantiateImageCodec(