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
32 changes: 28 additions & 4 deletions lib/web_ui/lib/src/engine/canvaskit/image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'dart:js_interop';
import 'dart:math' as math;
import 'dart:typed_data';

import 'package:meta/meta.dart';
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;
Expand Down Expand Up @@ -403,11 +404,13 @@ class CkImage implements ui.Image, StackTraceDebugger {
CkImage(SkImage skImage, {this.imageSource}) {
box = CountedRef<CkImage, SkImage>(skImage, this, 'SkImage');
_init();
imageSource?.refCount++;
}

CkImage.cloneOf(this.box, {this.imageSource}) {
_init();
box.ref(this);
imageSource?.refCount++;
}

void _init() {
Expand Down Expand Up @@ -454,6 +457,8 @@ class CkImage implements ui.Image, StackTraceDebugger {
ui.Image.onDispose?.call(this);
_disposed = true;
box.unref(this);

imageSource?.refCount--;
imageSource?.close();
}

Expand Down Expand Up @@ -645,7 +650,26 @@ sealed class ImageSource {
DomCanvasImageSource get canvasImageSource;
int get width;
int get height;
void close();

/// The number of references to this image source.
///
/// Calling [close] is a no-op if [refCount] is greater than 0.
///
/// Only when [refCount] is 0 will the [close] method actually close the
/// image source.
int refCount = 0;

@visibleForTesting
bool debugIsClosed = false;

void close() {
if (refCount == 0) {
_doClose();
debugIsClosed = true;
}
}

void _doClose();
}

class VideoFrameImageSource extends ImageSource {
Expand All @@ -654,7 +678,7 @@ class VideoFrameImageSource extends ImageSource {
final VideoFrame videoFrame;

@override
void close() {
void _doClose() {
// Do nothing. Skia will close the VideoFrame when the SkImage is disposed.
}

Expand All @@ -674,7 +698,7 @@ class ImageElementImageSource extends ImageSource {
final DomHTMLImageElement imageElement;

@override
void close() {
void _doClose() {
// There's no way to immediately close the <img> element. Just let the
// browser garbage collect it.
}
Expand All @@ -695,7 +719,7 @@ class ImageBitmapImageSource extends ImageSource {
final DomImageBitmap imageBitmap;

@override
void close() {
void _doClose() {
imageBitmap.close();
}

Expand Down
6 changes: 6 additions & 0 deletions lib/web_ui/lib/src/engine/dom.dart
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,12 @@ extension DomWindowExtension on DomWindow {
/// The Trusted Types API (when available).
/// See: https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API
external DomTrustedTypePolicyFactory? get trustedTypes;

@JS('createImageBitmap')
external JSPromise<JSAny?> _createImageBitmap(DomImageData source);
Future<DomImageBitmap> createImageBitmap(DomImageData source) {
return js_util.promiseToFuture<DomImageBitmap>(_createImageBitmap(source));
}
}

typedef DomRequestAnimationFrameCallback = void Function(JSNumber highResTime);
Expand Down
30 changes: 27 additions & 3 deletions lib/web_ui/test/canvaskit/image_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ import 'dart:typed_data';

import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine/canvaskit/image.dart';
import 'package:ui/src/engine/image_decoder.dart';
import 'package:ui/src/engine/util.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;

import 'common.dart';
import 'test_data.dart';

void main() {
internalBootstrapBrowserTest(() => testMain);
Expand Down Expand Up @@ -131,6 +130,31 @@ void testMain() {

expect(activeImages.length, 0);
});

test('CkImage does not close image source too early', () async {
final ImageSource imageSource = ImageBitmapImageSource(
await domWindow.createImageBitmap(createBlankDomImageData(4, 4)),
);

final SkImage skImage1 = canvasKit.MakeAnimatedImageFromEncoded(k4x4PngImage)!.makeImageAtCurrentFrame();
final CkImage image1 = CkImage(skImage1, imageSource: imageSource);

final SkImage skImage2 = canvasKit.MakeAnimatedImageFromEncoded(k4x4PngImage)!.makeImageAtCurrentFrame();
final CkImage image2 = CkImage(skImage2, imageSource: imageSource);

final CkImage image3 = image1.clone();

expect(imageSource.debugIsClosed, isFalse);

image1.dispose();
expect(imageSource.debugIsClosed, isFalse);

image2.dispose();
expect(imageSource.debugIsClosed, isFalse);

image3.dispose();
expect(imageSource.debugIsClosed, isTrue);
});
}

Future<ui.Image> _createImage() => _createPicture().toImage(10, 10);
Expand Down
Loading