diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/renderer.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/renderer.dart index 7f2e9ba31e..f94916d273 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/renderer.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/renderer.dart @@ -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 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, diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/html/renderer.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/html/renderer.dart index 96720c592c..5a47ba40c5 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/html/renderer.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/html/renderer.dart @@ -371,4 +371,9 @@ class HtmlRenderer implements Renderer { imageElement.src = await canvas.toDataUrl(); return completer.future; } + + @override + FutureOr createImageFromTextureSource(JSAny object, { required int width, required int height, required bool transferOwnership }) { + throw Exception('Not implemented for HTML renderer'); + } } diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/renderer.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/renderer.dart index 246ed9b6d1..3337ebf3ea 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/renderer.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/renderer.dart @@ -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 createImageFromImageBitmap(DomImageBitmap imageSource); + FutureOr createImageFromTextureSource(JSAny object, + {required int width, required int height, required bool transferOwnership}); + void decodeImageFromPixels( Uint8List pixels, int width, diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart index 695c734cfe..eba1da46d1 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart @@ -472,6 +472,19 @@ class SkwasmRenderer implements Renderer { surface.handle, )); } + + @override + FutureOr 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 { diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_stub/renderer.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_stub/renderer.dart index 024f588dda..2a2e6b5137 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_stub/renderer.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_stub/renderer.dart @@ -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.'); + } } diff --git a/engine/src/flutter/lib/web_ui/lib/ui_web/src/ui_web/images.dart b/engine/src/flutter/lib/web_ui/lib/ui_web/src/ui_web/images.dart index 5e297337f2..a21900200a 100644 --- a/engine/src/flutter/lib/web_ui/lib/ui_web/src/ui_web/images.dart +++ b/engine/src/flutter/lib/web_ui/lib/ui_web/src/ui_web/images.dart @@ -44,3 +44,17 @@ FutureOr 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 createImageFromTextureSource(JSAny object, { required int width, required int height, bool transferOwnership = false }) { + return renderer.createImageFromTextureSource( + object, width: width, height: height, transferOwnership: transferOwnership + ); +} diff --git a/engine/src/flutter/lib/web_ui/test/ui/image_golden_test.dart b/engine/src/flutter/lib/web_ui/test/ui/image_golden_test.dart index f126afb94a..198a74e893 100644 --- a/engine/src/flutter/lib/web_ui/test/ui/image_golden_test.dart +++ b/engine/src/flutter/lib/web_ui/test/ui/image_golden_test.dart @@ -420,6 +420,37 @@ Future 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([ + ''' + + + + ''' + ], { + 'type': 'image/svg+xml' + }); + final String url = domWindow.URL.createObjectURL(svgBlob); + final DomHTMLImageElement image = createDomHTMLImageElement(); + final Completer completer = Completer(); + 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(