Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -219,14 +219,31 @@ extension CanvasKitExtension on CanvasKit {
) => _MakeImage(info, pixels.toJS, bytesPerRow.toJS);

@JS('MakeLazyImageFromTextureSource')
external SkImage? _MakeLazyImageFromTextureSource(
external SkImage? _MakeLazyImageFromTextureSource2(
JSAny src,
SkPartialImageInfo info,
);
SkImage? MakeLazyImageFromTextureSource(

@JS('MakeLazyImageFromTextureSource')
external SkImage? _MakeLazyImageFromTextureSource3(
JSAny src,
JSNumber zeroSecondArgument,
JSBoolean srcIsPremultiplied,
);

SkImage? MakeLazyImageFromTextureSourceWithInfo(
Object src,
SkPartialImageInfo info,
) => _MakeLazyImageFromTextureSource(src.toJSAnyShallow, info);
) => _MakeLazyImageFromTextureSource2(src.toJSAnyShallow, info);

SkImage? MakeLazyImageFromImageBitmap(
DomImageBitmap imageBitmap,
bool hasPremultipliedAlpha,
) => _MakeLazyImageFromTextureSource3(
imageBitmap as JSAny,
0.toJS,
hasPremultipliedAlpha.toJS,
);
}

@JS('window.CanvasKitInit')
Expand Down
2 changes: 1 addition & 1 deletion lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class CkBrowserImageDecoder extends BrowserImageDecoder {

@override
ui.Image generateImageFromVideoFrame(VideoFrame frame) {
final SkImage? skImage = canvasKit.MakeLazyImageFromTextureSource(
final SkImage? skImage = canvasKit.MakeLazyImageFromTextureSourceWithInfo(
frame,
SkPartialImageInfo(
alphaType: canvasKit.AlphaType.Premul,
Expand Down
12 changes: 12 additions & 0 deletions lib/web_ui/lib/src/engine/canvaskit/renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,18 @@ class CanvasKitRenderer implements Renderer {
ui_web.ImageCodecChunkCallback? chunkCallback
}) => skiaInstantiateWebImageCodec(uri.toString(), chunkCallback);

@override
ui.Image createImageFromImageBitmap(DomImageBitmap imageBitmap) {
final SkImage? skImage = canvasKit.MakeLazyImageFromImageBitmap(
imageBitmap,
true
);
if (skImage == null) {
throw Exception('Failed to convert image bitmap to an SkImage.');
}
return CkImage(skImage);
}

@override
void decodeImageFromPixels(
Uint8List pixels,
Expand Down
41 changes: 39 additions & 2 deletions lib/web_ui/lib/src/engine/dom.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1433,6 +1433,33 @@ extension DomImageBitmapExtension on DomImageBitmap {
external void close();
}


@JS('createImageBitmap')
external JSPromise _createImageBitmap1(
JSAny source,
);
@JS('createImageBitmap')
external JSPromise _createImageBitmap2(
JSAny source,
JSNumber x,
JSNumber y,
JSNumber width,
JSNumber height,
);
JSPromise createImageBitmap(JSAny source, [({int x, int y, int width, int height})? bounds]) {
if (bounds != null) {
return _createImageBitmap2(
source,
bounds.x.toJS,
bounds.y.toJS,
bounds.width.toJS,
bounds.height.toJS
);
} else {
return _createImageBitmap1(source);
}
}

@JS()
@staticInterop
class DomCanvasPattern {}
Expand Down Expand Up @@ -2264,14 +2291,24 @@ extension DomURLExtension on DomURL {
@staticInterop
class DomBlob {
external factory DomBlob(JSArray parts);

external factory DomBlob.withOptions(JSArray parts, JSAny options);
}

extension DomBlobExtension on DomBlob {
external JSPromise arrayBuffer();
}

DomBlob createDomBlob(List<Object?> parts) =>
DomBlob(parts.toJSAnyShallow as JSArray);
DomBlob createDomBlob(List<Object?> parts, [Map<String, dynamic>? options]) {
if (options == null) {
return DomBlob(parts.toJSAnyShallow as JSArray);
} else {
return DomBlob.withOptions(
parts.toJSAnyShallow as JSArray,
options.toJSAnyDeep
);
}
}

typedef DomMutationCallback = void Function(
JSArray mutation, DomMutationObserver observer);
Expand Down
28 changes: 28 additions & 0 deletions lib/web_ui/lib/src/engine/html/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 @@ -361,4 +362,31 @@ class HtmlRenderer implements Renderer {
baseline: baseline,
lineNumber: lineNumber
);

@override
Future<ui.Image> createImageFromImageBitmap(DomImageBitmap imageSource) async {
final int width = imageSource.width.toDartInt;
final int height = imageSource.height.toDartInt;
final OffScreenCanvas canvas = OffScreenCanvas(width, height);
final DomCanvasRenderingContextBitmapRenderer context = canvas.getBitmapRendererContext()!;
context.transferFromImageBitmap(imageSource);
final DomHTMLImageElement imageElement = createDomHTMLImageElement();
late final DomEventListener loadListener;
late final DomEventListener errorListener;
final Completer<HtmlImage> completer = Completer<HtmlImage>();
loadListener = createDomEventListener((DomEvent event) {
completer.complete(HtmlImage(imageElement, width, height));
imageElement.removeEventListener('load', loadListener);
imageElement.removeEventListener('error', errorListener);
});
errorListener = createDomEventListener((DomEvent event) {
completer.completeError(Exception('Failed to create image from image bitmap.'));
imageElement.removeEventListener('load', loadListener);
imageElement.removeEventListener('error', errorListener);
});
imageElement.addEventListener('load', loadListener);
imageElement.addEventListener('error', errorListener);
imageElement.src = await canvas.toDataUrl();
return completer.future;
}
}
10 changes: 3 additions & 7 deletions lib/web_ui/lib/src/engine/renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,11 @@ import 'dart:async';
import 'dart:math' as math;
import 'dart:typed_data';

import 'package:ui/src/engine.dart';
import 'package:ui/src/engine/skwasm/skwasm_stub.dart' if (dart.library.ffi) 'package:ui/src/engine/skwasm/skwasm_impl.dart';
import 'package:ui/ui.dart' as ui;
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;

import 'browser_detection.dart';
import 'canvaskit/renderer.dart';
import 'configuration.dart';
import 'embedder.dart';
import 'fonts.dart';
import 'html/renderer.dart';

final Renderer _renderer = Renderer._internal();
Renderer get renderer => _renderer;

Expand Down Expand Up @@ -134,6 +128,8 @@ abstract class Renderer {
ui_web.ImageCodecChunkCallback? chunkCallback,
});

FutureOr<ui.Image> createImageFromImageBitmap(DomImageBitmap imageSource);

void decodeImageFromPixels(
Uint8List pixels,
int width,
Expand Down
6 changes: 6 additions & 0 deletions lib/web_ui/lib/src/engine/safe_browser_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,12 @@ class OffScreenCanvas {
: canvasElement!.getContext('2d');
}

DomCanvasRenderingContextBitmapRenderer? getBitmapRendererContext() {
return (offScreenCanvas != null
? offScreenCanvas!.getContext('bitmaprenderer')
: canvasElement!.getContext('bitmaprenderer')) as DomCanvasRenderingContextBitmapRenderer?;
}

/// Feature detection for transferToImageBitmap on OffscreenCanvas.
bool get transferToImageBitmapSupported =>
js_util.hasProperty(offScreenCanvas!, 'transferToImageBitmap');
Expand Down
6 changes: 4 additions & 2 deletions lib/web_ui/lib/src/engine/skwasm/skwasm_impl/codecs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:js_interop';

import 'package:ui/src/engine.dart';
import 'package:ui/src/engine/skwasm/skwasm_impl.dart';
import 'package:ui/ui.dart' as ui;
Expand All @@ -18,8 +20,8 @@ class SkwasmImageDecoder extends BrowserImageDecoder {
final int width = frame.codedWidth.toInt();
final int height = frame.codedHeight.toInt();
final SkwasmSurface surface = (renderer as SkwasmRenderer).surface;
return SkwasmImage(imageCreateFromVideoFrame(
frame,
return SkwasmImage(imageCreateFromTextureSource(
frame as JSAny,
width,
height,
surface.handle,
Expand Down
19 changes: 9 additions & 10 deletions lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ library skwasm_impl;
import 'dart:ffi';
import 'dart:js_interop';

import 'package:ui/src/engine.dart';
import 'package:ui/src/engine/skwasm/skwasm_impl.dart';

final class RawImage extends Opaque {}
Expand Down Expand Up @@ -47,9 +46,9 @@ external ImageHandle imageCreateFromPixels(
// Int,
// Int,
// SurfaceHandle,
// )>(symbol: 'image_createFromVideoFrame', isLeaf: true)
// external ImageHandle imageCreateFromVideoFrame(
// JSAny videoFrame,
// )>(symbol: 'image_createFromTextureSource', isLeaf: true)
// external ImageHandle imageCreateFromTextureSource(
// JSAny textureSource,
// int width,
// int height,
// SurfaceHandle handle,
Expand All @@ -59,21 +58,21 @@ external ImageHandle imageCreateFromPixels(
// annotations currently. For now, we can use JS interop to expose this function
// instead.
extension SkwasmImageExtension on SkwasmInstance {
@JS('wasmExports.image_createFromVideoFrame')
external JSNumber imageCreateFromVideoFrame(
VideoFrame frame,
@JS('wasmExports.image_createFromTextureSource')
external JSNumber imageCreateFromTextureSource(
JSAny textureSource,
JSNumber width,
JSNumber height,
JSNumber surfaceHandle,
);
}
ImageHandle imageCreateFromVideoFrame(
VideoFrame frame,
ImageHandle imageCreateFromTextureSource(
JSAny frame,
int width,
int height,
SurfaceHandle handle
) => ImageHandle.fromAddress(
skwasmInstance.imageCreateFromVideoFrame(
skwasmInstance.imageCreateFromTextureSource(
frame,
width.toJS,
height.toJS,
Expand Down
10 changes: 10 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 @@ -448,6 +448,16 @@ class SkwasmRenderer implements Renderer {
baseline: baseline,
lineNumber: lineNumber
);

@override
ui.Image createImageFromImageBitmap(DomImageBitmap imageSource) {
return SkwasmImage(imageCreateFromTextureSource(
imageSource as JSAny,
imageSource.width.toDartInt,
imageSource.height.toDartInt,
surface.handle,
));
}
}

class SkwasmPictureRenderer implements PictureRenderer {
Expand Down
5 changes: 5 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 @@ -189,4 +189,9 @@ class SkwasmRenderer implements Renderer {
required double baseline,
required int lineNumber
}) => throw UnimplementedError('Skwasm not implemented on this platform.');

@override
ui.Image createImageFromImageBitmap(DomImageBitmap imageSource) {
throw UnimplementedError('Skwasm not implemented on this platform.');
}
}
19 changes: 19 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 @@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'dart:js_interop';

import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;

Expand All @@ -25,3 +28,19 @@ Future<ui.Codec> createImageCodecFromUrl(
chunkCallback: chunkCallback,
);
}

/// Creates a [ui.Image] from an ImageBitmap object.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: dartdoc likes it when there's one empty line between the first and the second paragraph (or between paragraphs in general).

///
/// The contents of the ImageBitmap must have a premultiplied alpha.
/// The engine will take ownership of the ImageBitmap object and consume its
/// contents.
///
/// See https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap
FutureOr<ui.Image> createImageFromImageBitmap(JSAny imageSource) {
if (!domInstanceOfString(imageSource, 'ImageBitmap')) {
throw ArgumentError('Image source $imageSource is not an ImageBitmap.', 'imageSource');
}
return renderer.createImageFromImageBitmap(
imageSource as DomImageBitmap,
);
}
32 changes: 17 additions & 15 deletions lib/web_ui/skwasm/image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,20 +79,22 @@ class ExternalWebGLTexture : public GrExternalTexture {
};
} // namespace

class VideoFrameImageGenerator : public GrExternalTextureGenerator {
class TextureSourceImageGenerator : public GrExternalTextureGenerator {
public:
VideoFrameImageGenerator(SkImageInfo ii,
SkwasmObject videoFrame,
Skwasm::Surface* surface)
TextureSourceImageGenerator(SkImageInfo ii,
SkwasmObject textureSource,
Skwasm::Surface* surface)
: GrExternalTextureGenerator(ii),
_videoFrameWrapper(surface->createVideoFrameWrapper(videoFrame)) {}
_textureSourceWrapper(
surface->createTextureSourceWrapper(textureSource)) {}

std::unique_ptr<GrExternalTexture> generateExternalTexture(
GrRecordingContext* context,
GrMipMapped mipmapped) override {
GrGLTextureInfo glInfo;
glInfo.fID = skwasm_createGlTextureFromVideoFrame(
_videoFrameWrapper->getVideoFrame(), fInfo.width(), fInfo.height());
glInfo.fID = skwasm_createGlTextureFromTextureSource(
_textureSourceWrapper->getTextureSource(), fInfo.width(),
fInfo.height());
glInfo.fFormat = GL_RGBA8_OES;
glInfo.fTarget = GL_TEXTURE_2D;

Expand All @@ -103,7 +105,7 @@ class VideoFrameImageGenerator : public GrExternalTextureGenerator {
}

private:
std::unique_ptr<Skwasm::VideoFrameWrapper> _videoFrameWrapper;
std::unique_ptr<Skwasm::TextureSourceWrapper> _textureSourceWrapper;
};

SKWASM_EXPORT SkImage* image_createFromPicture(SkPicture* picture,
Expand All @@ -129,17 +131,17 @@ SKWASM_EXPORT SkImage* image_createFromPixels(SkData* data,
.release();
}

SKWASM_EXPORT SkImage* image_createFromVideoFrame(SkwasmObject videoFrame,
int width,
int height,
Skwasm::Surface* surface) {
SKWASM_EXPORT SkImage* image_createFromTextureSource(SkwasmObject textureSource,
int width,
int height,
Skwasm::Surface* surface) {
return SkImages::DeferredFromTextureGenerator(
std::unique_ptr<VideoFrameImageGenerator>(
new VideoFrameImageGenerator(
std::unique_ptr<TextureSourceImageGenerator>(
new TextureSourceImageGenerator(
SkImageInfo::Make(width, height,
SkColorType::kRGBA_8888_SkColorType,
SkAlphaType::kPremul_SkAlphaType),
videoFrame, surface)))
textureSource, surface)))
.release();
}

Expand Down
Loading