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
56 changes: 35 additions & 21 deletions lib/web_ui/lib/src/engine/canvaskit/image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,33 @@ void skiaInstantiateImageCodec(Uint8List list, Callback<ui.Codec> callback,
callback(codec);
}

/// Instantiates a [ui.Codec] backed by an `SkAnimatedImage` from Skia after requesting from URI.
void skiaInstantiateWebImageCodec(String src, Callback<ui.Codec> callback,
WebOnlyImageCodecChunkCallback? chunkCallback) {
chunkCallback?.call(0, 100);
/// Instantiates a [ui.Codec] backed by an `SkAnimatedImage` from Skia after
/// requesting from URI.
Future<ui.Codec> skiaInstantiateWebImageCodec(
String src, WebOnlyImageCodecChunkCallback? chunkCallback) {
Completer<ui.Codec> completer = Completer<ui.Codec>();
//TODO: Switch to using MakeImageFromCanvasImageSource when animated images are supported.
html.HttpRequest.request(
src,
responseType: "arraybuffer",
).then((html.HttpRequest response) {
chunkCallback?.call(100, 100);
html.HttpRequest.request(src, responseType: "arraybuffer",
onProgress: (html.ProgressEvent event) {
if (event.lengthComputable) {
chunkCallback?.call(event.loaded!, event.total!);
}
}).then((html.HttpRequest response) {
if (response.status != 200) {
completer.completeError(Exception(
'Network image request failed with status: ${response.status}'));
}
final Uint8List list =
new Uint8List.view((response.response as ByteBuffer));
final SkAnimatedImage skAnimatedImage =
canvasKit.MakeAnimatedImageFromEncoded(list);
final CkAnimatedImage animatedImage = CkAnimatedImage(skAnimatedImage);
final CkAnimatedImageCodec codec = CkAnimatedImageCodec(animatedImage);
callback(codec);
completer.complete(codec);
}, onError: (dynamic error) {
completer.completeError(error);
});
return completer.future;
}

/// A wrapper for `SkAnimatedImage`.
Expand All @@ -43,7 +52,8 @@ class CkAnimatedImage implements ui.Image {
// being garbage-collected, or by an explicit call to [delete].
late final SkiaObjectBox box;

CkAnimatedImage(SkAnimatedImage skAnimatedImage) : this._(skAnimatedImage, null);
CkAnimatedImage(SkAnimatedImage skAnimatedImage)
: this._(skAnimatedImage, null);

CkAnimatedImage._(this._skAnimatedImage, SkiaObjectBox? boxToClone) {
if (boxToClone != null) {
Expand All @@ -66,19 +76,21 @@ class CkAnimatedImage implements ui.Image {
if (assertionsEnabled) {
return _disposed;
}
throw StateError('Image.debugDisposed is only available when asserts are enabled.');
throw StateError(
'Image.debugDisposed is only available when asserts are enabled.');
}

ui.Image clone() => CkAnimatedImage._(_skAnimatedImage, box);

@override
bool isCloneOf(ui.Image other) {
return other is CkAnimatedImage
&& other._skAnimatedImage.isAliasOf(_skAnimatedImage);
return other is CkAnimatedImage &&
other._skAnimatedImage.isAliasOf(_skAnimatedImage);
}

@override
List<StackTrace>? debugGetOpenHandleStackTraces() => box.debugGetStackTraces();
List<StackTrace>? debugGetOpenHandleStackTraces() =>
box.debugGetStackTraces();

int get frameCount => _skAnimatedImage.getFrameCount();

Expand Down Expand Up @@ -115,8 +127,9 @@ class CkAnimatedImage implements ui.Image {
);
bytes = _skAnimatedImage.readPixels(imageInfo, 0, 0);
} else {
final SkData skData = _skAnimatedImage.encodeToData(); //defaults to PNG 100%
// make a copy that we can return
// Defaults to PNG 100%.
final SkData skData = _skAnimatedImage.encodeToData();
// Make a copy that we can return.
bytes = Uint8List.fromList(canvasKit.getSkDataBytes(skData));
}

Expand Down Expand Up @@ -162,20 +175,21 @@ class CkImage implements ui.Image {
if (assertionsEnabled) {
return _disposed;
}
throw StateError('Image.debugDisposed is only available when asserts are enabled.');
throw StateError(
'Image.debugDisposed is only available when asserts are enabled.');
}

@override
ui.Image clone() => CkImage._(skImage, box);

@override
bool isCloneOf(ui.Image other) {
return other is CkImage
&& other.skImage.isAliasOf(skImage);
return other is CkImage && other.skImage.isAliasOf(skImage);
}

@override
List<StackTrace>? debugGetOpenHandleStackTraces() => box.debugGetStackTraces();
List<StackTrace>? debugGetOpenHandleStackTraces() =>
box.debugGetStackTraces();

@override
int get width => skImage.width();
Expand Down
18 changes: 9 additions & 9 deletions lib/web_ui/lib/src/ui/painting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -462,23 +462,23 @@ String? _instantiateImageCodec(Uint8List list, engine.Callback<Codec> callback)
}

Future<Codec> webOnlyInstantiateImageCodecFromUrl(Uri uri,
{engine.WebOnlyImageCodecChunkCallback? chunkCallback}) {
return _futurize<Codec>((engine.Callback<Codec> callback) =>
{engine.WebOnlyImageCodecChunkCallback? chunkCallback}) {
if (engine.useCanvasKit) {
return engine.skiaInstantiateWebImageCodec(
uri.toString(), chunkCallback);
} else {
return _futurize<Codec>((engine.Callback<Codec> callback) =>
_instantiateImageCodecFromUrl(uri, chunkCallback, callback));
}
}

String? _instantiateImageCodecFromUrl(
Uri uri,
engine.WebOnlyImageCodecChunkCallback? chunkCallback,
engine.Callback<Codec> callback,
) {
if (engine.useCanvasKit) {
engine.skiaInstantiateWebImageCodec(uri.toString(), callback, chunkCallback);
return null;
} else {
callback(engine.HtmlCodec(uri.toString(), chunkCallback: chunkCallback));
return null;
}
callback(engine.HtmlCodec(uri.toString(), chunkCallback: chunkCallback));
return null;
}

void decodeImageFromList(Uint8List list, ImageDecoderCallback callback) {
Expand Down
31 changes: 24 additions & 7 deletions lib/web_ui/test/canvaskit/image_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// found in the LICENSE file.

// @dart = 2.6
import 'dart:html' show ProgressEvent;

import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
Expand All @@ -22,14 +24,16 @@ void testMain() {
});

test('CkAnimatedImage toString', () {
final SkAnimatedImage skAnimatedImage = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage);
final SkAnimatedImage skAnimatedImage =
canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage);
final CkAnimatedImage image = CkAnimatedImage(skAnimatedImage);
expect(image.toString(), '[1×1]');
image.dispose();
});

test('CkAnimatedImage can be explicitly disposed of', () {
final SkAnimatedImage skAnimatedImage = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage);
final SkAnimatedImage skAnimatedImage =
canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage);
final CkAnimatedImage image = CkAnimatedImage(skAnimatedImage);
expect(image.box.isDeleted, false);
expect(image.debugDisposed, false);
Expand All @@ -42,7 +46,8 @@ void testMain() {
});

test('CkAnimatedImage can be cloned and explicitly disposed of', () async {
final SkAnimatedImage skAnimatedImage = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage);
final SkAnimatedImage skAnimatedImage =
canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage);
final CkAnimatedImage image = CkAnimatedImage(skAnimatedImage);
final CkAnimatedImage imageClone = image.clone();

Expand All @@ -63,14 +68,18 @@ void testMain() {
});

test('CkImage toString', () {
final SkImage skImage = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage).getCurrentFrame();
final SkImage skImage =
canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)
.getCurrentFrame();
final CkImage image = CkImage(skImage);
expect(image.toString(), '[1×1]');
image.dispose();
});

test('CkImage can be explicitly disposed of', () {
final SkImage skImage = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage).getCurrentFrame();
final SkImage skImage =
canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)
.getCurrentFrame();
final CkImage image = CkImage(skImage);
expect(image.debugDisposed, false);
expect(image.box.isDeleted, false);
Expand All @@ -83,7 +92,9 @@ void testMain() {
});

test('CkImage can be explicitly disposed of when cloned', () async {
final SkImage skImage = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage).getCurrentFrame();
final SkImage skImage =
canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)
.getCurrentFrame();
final CkImage image = CkImage(skImage);
final CkImage imageClone = image.clone();

Expand All @@ -102,6 +113,12 @@ void testMain() {
await Future<void>.delayed(Duration.zero);
expect(skImage.isDeleted(), true);
});
// TODO: https://github.com/flutter/flutter/issues/60040

test('skiaInstantiateWebImageCodec throws exception if given invalid URL',
() async {
expect(skiaInstantiateWebImageCodec('invalid-url', null),
throwsA(isA<ProgressEvent>()));
});
// TODO: https://github.com/flutter/flutter/issues/60040
}, skip: isIosSafari);
}