Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Closed
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
3 changes: 2 additions & 1 deletion lib/web_ui/lib/src/engine/browser_detection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import 'package:meta/meta.dart';

import 'dom.dart';
import 'safe_browser_api.dart';

// iOS 15 launched WebGL 2.0, but there's something broken about it, which
// leads to apps failing to load. For now, we're forcing WebGL 1 on iOS.
Expand Down Expand Up @@ -269,4 +270,4 @@ int _detectWebGLVersion() {

/// Whether the current browser supports the Chromium variant of CanvasKit.
bool get browserSupportsCanvaskitChromium =>
domIntl.v8BreakIterator != null && domIntl.Segmenter != null;
domIntl.v8BreakIterator != null && domIntl.Segmenter != null && browserSupportsImageDecoder;
19 changes: 14 additions & 5 deletions lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import 'renderer.dart';
/// Entrypoint into the CanvasKit API.
late CanvasKit canvasKit;

/// Whether the [canvasKit] being used is a Chromium variant.
final bool isChromiumVariant = canvasKit.ParagraphBuilder.RequiresClientICU();

bool get _enableCanvasKitChromiumInAutoMode => browserSupportsCanvaskitChromium;

/// Sets the [CanvasKit] object on `window` so we can use `@JS()` to bind to
Expand Down Expand Up @@ -76,8 +79,14 @@ extension CanvasKitExtension on CanvasKit {
@JS('MakeAnimatedImageFromEncoded')
external SkAnimatedImage? _MakeAnimatedImageFromEncoded(
JSUint8Array imageData);
SkAnimatedImage? MakeAnimatedImageFromEncoded(Uint8List imageData) =>
_MakeAnimatedImageFromEncoded(imageData.toJS);
SkAnimatedImage? MakeAnimatedImageFromEncoded(Uint8List imageData) {
assert(
!isChromiumVariant,
'CanvasKit.MakeAnimatedImageFromEncoded cannot be used with the Chromium '
'build of CanvasKit.',
);
return _MakeAnimatedImageFromEncoded(imageData.toJS);
}

external SkShaderNamespace get Shader;
external SkMaskFilterNamespace get MaskFilter;
Expand Down Expand Up @@ -1153,10 +1162,10 @@ extension SkImageExtension on SkImage {
matrix?.toJS);

@JS('readPixels')
external JSUint8Array _readPixels(
external JSUint8Array? _readPixels(
JSNumber srcX, JSNumber srcY, SkImageInfo imageInfo);
Uint8List readPixels(double srcX, double srcY, SkImageInfo imageInfo) =>
_readPixels(srcX.toJS, srcY.toJS, imageInfo).toDart;
Uint8List? readPixels(double srcX, double srcY, SkImageInfo imageInfo) =>
_readPixels(srcX.toJS, srcY.toJS, imageInfo)?.toDart;

@JS('encodeToBytes')
external JSUint8Array? _encodeToBytes();
Expand Down
35 changes: 26 additions & 9 deletions lib/web_ui/lib/src/engine/canvaskit/image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,34 @@ import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;

/// Instantiates a [ui.Codec] backed by an `SkAnimatedImage` from Skia.
FutureOr<ui.Codec> skiaInstantiateImageCodec(Uint8List list,
[int? targetWidth, int? targetHeight]) {
// If we have either a target width or target height, use canvaskit to decode.
if (browserSupportsImageDecoder && (targetWidth == null && targetHeight == null)) {
return CkBrowserImageDecoder.create(
data: list,
debugSource: 'encoded image bytes',
FutureOr<ui.Codec> skiaInstantiateImageCodec(
Uint8List list, [
int? targetWidth,
int? targetHeight,
]) async {
if (!browserSupportsImageDecoder) {
return CkAnimatedImage.decodeFromBytes(
list,
'encoded image bytes',
targetWidth: targetWidth,
targetHeight: targetHeight,
);
} else {
return CkAnimatedImage.decodeFromBytes(list, 'encoded image bytes', targetWidth: targetWidth, targetHeight: targetHeight);
}

final CkBrowserImageDecoder baseDecoder = await CkBrowserImageDecoder.create(
data: list,
debugSource: 'encoded image bytes',
);

if (targetWidth == null && targetHeight == null) {
return baseDecoder;
}

return ResizingCodec(
baseDecoder,
targetWidth: targetWidth,
targetHeight: targetHeight,
);
}

void skiaDecodeImageFromPixels(
Expand Down
11 changes: 10 additions & 1 deletion lib/web_ui/lib/src/engine/canvaskit/image_wasm_codecs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,16 @@ import 'package:ui/ui.dart' as ui;
/// Wraps `SkAnimatedImage`.
class CkAnimatedImage implements ui.Codec {
/// Decodes an image from a list of encoded bytes.
CkAnimatedImage.decodeFromBytes(this._bytes, this.src, {this.targetWidth, this.targetHeight}) {
CkAnimatedImage.decodeFromBytes(
this._bytes,
this.src, {
this.targetWidth,
this.targetHeight,
}) : assert(
!isChromiumVariant,
'CkAnimatedImage.decodeFromBytes cannot be used with the Chromium '
'build of CanvasKit.',
) {
final SkAnimatedImage skAnimatedImage = createSkAnimatedImage();
_ref = UniqueRef<SkAnimatedImage>(this, skAnimatedImage, 'Codec');
}
Expand Down
5 changes: 4 additions & 1 deletion lib/web_ui/lib/src/engine/canvaskit/picture.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,10 @@ class CkPicture implements ui.Picture {
width: width.toDouble(),
height: height.toDouble(),
);
final Uint8List pixels = skImage.readPixels(0, 0, imageInfo);
final Uint8List? pixels = skImage.readPixels(0, 0, imageInfo);
if (pixels == null) {
throw StateError('Unable to read pixels from SkImage.');
}
final SkImage? rasterImage = canvasKit.MakeImage(imageInfo, pixels, (4 * width).toDouble());
if (rasterImage == null) {
throw StateError('Unable to convert image pixels into SkImage.');
Expand Down
4 changes: 1 addition & 3 deletions lib/web_ui/lib/src/engine/canvaskit/text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;

final bool _ckRequiresClientICU = canvasKit.ParagraphBuilder.RequiresClientICU();

final List<String> _testFonts = <String>['FlutterTest', 'Ahem'];
String? _effectiveFontFamily(String? fontFamily) {
return ui_web.debugEmulateFlutterTesterEnvironment && !_testFonts.contains(fontFamily)
Expand Down Expand Up @@ -882,7 +880,7 @@ class CkParagraphBuilder implements ui.ParagraphBuilder {

/// Builds the CkParagraph with the builder and deletes the builder.
SkParagraph _buildSkParagraph() {
if (_ckRequiresClientICU) {
if (isChromiumVariant) {
injectClientICU(_paragraphBuilder);
}
final SkParagraph result = _paragraphBuilder.build();
Expand Down
2 changes: 1 addition & 1 deletion lib/web_ui/lib/src/engine/canvaskit/text_fragmenter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ extension SegmentationCacheExtensions on SegmentationCache {
/// without ICU data.
void injectClientICU(SkParagraphBuilder builder) {
assert(
canvasKit.ParagraphBuilder.RequiresClientICU(),
isChromiumVariant,
'This method should only be used with the CanvasKit Chromium variant.',
);

Expand Down
139 changes: 73 additions & 66 deletions lib/web_ui/test/canvaskit/canvaskit_api_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ void _imageTests() {
),
isNotNull,
);
});
}, skip: !canvasKitContainsCodecs);

test('MakeAnimatedImageFromEncoded makes an animated image', () {
final SkAnimatedImage animated =
Expand All @@ -311,6 +311,13 @@ void _imageTests() {
expect(frame.height(), 1);
expect(animated.decodeNextFrame(), 100);
}
}, skip: !canvasKitContainsCodecs);

test('MakeAnimatedImageFromEncoded throws with Chromium variant', () {
expect(
() => canvasKit.MakeAnimatedImageFromEncoded(kAnimatedGif),
canvasKitContainsCodecs ? returnsNormally : throwsAssertionError,
);
});
}

Expand Down Expand Up @@ -1153,7 +1160,7 @@ void _canvasTests() {
canvasKit.BlendMode.SrcOver,
Uint32List.fromList(<int>[0xff000000, 0xffffffff]),
);
});
}, skip: !canvasKitContainsCodecs);

test('drawCircle', () {
canvas.drawCircle(1, 2, 3, SkPaint());
Expand All @@ -1171,69 +1178,71 @@ void _canvasTests() {
);
});

test('drawImageOptions', () {
final SkAnimatedImage image =
canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)!;
canvas.drawImageOptions(
image.makeImageAtCurrentFrame(),
10,
20,
canvasKit.FilterMode.Linear,
canvasKit.MipmapMode.None,
SkPaint(),
);
});
group('[wasm codecs]', () {
test('drawImageOptions', () {
final SkAnimatedImage image =
canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)!;
canvas.drawImageOptions(
image.makeImageAtCurrentFrame(),
10,
20,
canvasKit.FilterMode.Linear,
canvasKit.MipmapMode.None,
SkPaint(),
);
});

test('drawImageCubic', () {
final SkAnimatedImage image =
canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)!;
canvas.drawImageCubic(
image.makeImageAtCurrentFrame(),
10,
20,
0.3,
0.3,
SkPaint(),
);
});
test('drawImageCubic', () {
final SkAnimatedImage image =
canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)!;
canvas.drawImageCubic(
image.makeImageAtCurrentFrame(),
10,
20,
0.3,
0.3,
SkPaint(),
);
});

test('drawImageRectOptions', () {
final SkAnimatedImage image =
canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)!;
canvas.drawImageRectOptions(
image.makeImageAtCurrentFrame(),
Float32List.fromList(<double>[0, 0, 1, 1]),
Float32List.fromList(<double>[0, 0, 1, 1]),
canvasKit.FilterMode.Linear,
canvasKit.MipmapMode.None,
SkPaint(),
);
});
test('drawImageRectOptions', () {
final SkAnimatedImage image =
canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)!;
canvas.drawImageRectOptions(
image.makeImageAtCurrentFrame(),
Float32List.fromList(<double>[0, 0, 1, 1]),
Float32List.fromList(<double>[0, 0, 1, 1]),
canvasKit.FilterMode.Linear,
canvasKit.MipmapMode.None,
SkPaint(),
);
});

test('drawImageRectCubic', () {
final SkAnimatedImage image =
canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)!;
canvas.drawImageRectCubic(
image.makeImageAtCurrentFrame(),
Float32List.fromList(<double>[0, 0, 1, 1]),
Float32List.fromList(<double>[0, 0, 1, 1]),
0.3,
0.3,
SkPaint(),
);
});
test('drawImageRectCubic', () {
final SkAnimatedImage image =
canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)!;
canvas.drawImageRectCubic(
image.makeImageAtCurrentFrame(),
Float32List.fromList(<double>[0, 0, 1, 1]),
Float32List.fromList(<double>[0, 0, 1, 1]),
0.3,
0.3,
SkPaint(),
);
});

test('drawImageNine', () {
final SkAnimatedImage image =
canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)!;
canvas.drawImageNine(
image.makeImageAtCurrentFrame(),
Float32List.fromList(<double>[0, 0, 1, 1]),
Float32List.fromList(<double>[0, 0, 1, 1]),
canvasKit.FilterMode.Linear,
SkPaint(),
);
});
test('drawImageNine', () {
final SkAnimatedImage image =
canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)!;
canvas.drawImageNine(
image.makeImageAtCurrentFrame(),
Float32List.fromList(<double>[0, 0, 1, 1]),
Float32List.fromList(<double>[0, 0, 1, 1]),
canvasKit.FilterMode.Linear,
SkPaint(),
);
});
}, skip: !canvasKitContainsCodecs);

test('drawLine', () {
canvas.drawLine(0, 1, 2, 3, SkPaint());
Expand Down Expand Up @@ -1615,7 +1624,7 @@ void _paragraphTests() {
builder.pushStyle(
canvasKit.TextStyle(SkTextStyleProperties()..halfLeading = true));
builder.pop();
if (canvasKit.ParagraphBuilder.RequiresClientICU()) {
if (isChromiumVariant) {
injectClientICU(builder);
}
final SkParagraph paragraph = builder.build();
Expand Down Expand Up @@ -1733,7 +1742,7 @@ void _paragraphTests() {
);
builder.addText('hello');

if (canvasKit.ParagraphBuilder.RequiresClientICU()) {
if (isChromiumVariant) {
injectClientICU(builder);
}

Expand Down Expand Up @@ -1873,11 +1882,9 @@ void _paragraphTests() {

v8BreakIterator = Object();
browserSupportsImageDecoder = false;
// TODO(mdebbar): we don't check image codecs for now.
// https://github.com/flutter/flutter/issues/122331
expect(getCanvasKitJsFileNames(CanvasKitVariant.full), <String>['canvaskit.js']);
expect(getCanvasKitJsFileNames(CanvasKitVariant.chromium), <String>['chromium/canvaskit.js']);
expect(getCanvasKitJsFileNames(CanvasKitVariant.auto), <String>['chromium/canvaskit.js', 'canvaskit.js']);
expect(getCanvasKitJsFileNames(CanvasKitVariant.auto), <String>['canvaskit.js']);

v8BreakIterator = null;
browserSupportsImageDecoder = false;
Expand Down
3 changes: 3 additions & 0 deletions lib/web_ui/test/canvaskit/common.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import '../common/test_initialization.dart';

const MethodCodec codec = StandardMethodCodec();

bool get canvasKitContainsCodecs =>
configuration.canvasKitVariant == CanvasKitVariant.full;

/// Common test setup for all CanvasKit unit-tests.
void setUpCanvasKitTest() {
setUpUnitTests(
Expand Down
Loading