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

Add createImageFromTextureSource method to ui_web #53483

Merged
merged 11 commits into from
Jun 21, 2024
24 changes: 24 additions & 0 deletions lib/web_ui/lib/src/engine/canvaskit/renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions lib/web_ui/lib/src/engine/html/renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
}
4 changes: 4 additions & 0 deletions lib/web_ui/lib/src/engine/renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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,
Expand Down
13 changes: 13 additions & 0 deletions lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions lib/web_ui/lib/src/engine/skwasm/skwasm_stub/renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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.');
}
}
14 changes: 14 additions & 0 deletions lib/web_ui/lib/ui_web/src/ui_web/images.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}
31 changes: 31 additions & 0 deletions lib/web_ui/test/ui/image_golden_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down