-
Notifications
You must be signed in to change notification settings - Fork 6k
Implement Image.clone for CanvasKit #21555
Changes from 3 commits
cd9cb1b
1b3ae46
a1deae3
49ed2fa
4594551
38f0221
f1727e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,10 +41,16 @@ class CkAnimatedImage implements ui.Image { | |
|
|
||
| // Use a box because `SkImage` may be deleted either due to this object | ||
| // being garbage-collected, or by an explicit call to [delete]. | ||
| late final SkiaObjectBox box; | ||
| late final RefCountedSkiaObjectBox box; | ||
|
|
||
| CkAnimatedImage(this._skAnimatedImage) { | ||
| box = SkiaObjectBox(this, _skAnimatedImage as SkDeletable); | ||
| CkAnimatedImage(SkAnimatedImage skAnimatedImage) : this._(skAnimatedImage, null); | ||
|
|
||
| CkAnimatedImage._(this._skAnimatedImage, RefCountedSkiaObjectBox? boxToClone) { | ||
| if (boxToClone != null) { | ||
| box = boxToClone.clone(this); | ||
| } else { | ||
| box = RefCountedSkiaObjectBox(this, _skAnimatedImage as SkDeletable); | ||
| } | ||
| } | ||
|
|
||
| @override | ||
|
|
@@ -53,13 +59,17 @@ class CkAnimatedImage implements ui.Image { | |
| } | ||
|
|
||
| @override | ||
| ui.Image clone() => this; | ||
| ui.Image clone() => CkAnimatedImage._(_skAnimatedImage, box); | ||
|
|
||
| @override | ||
| bool isCloneOf(ui.Image other) => other == this; | ||
| bool isCloneOf(ui.Image other) { | ||
| return other is CkAnimatedImage | ||
| && other._skAnimatedImage == _skAnimatedImage; | ||
| } | ||
|
|
||
| @override | ||
| List<StackTrace>? debugGetOpenHandleStackTraces() => null; | ||
| List<StackTrace>? debugGetOpenHandleStackTraces() => box.debugGetStackTraces(); | ||
|
|
||
| int get frameCount => _skAnimatedImage.getFrameCount(); | ||
|
|
||
| /// Decodes the next frame and returns the frame duration. | ||
|
|
@@ -114,10 +124,16 @@ class CkImage implements ui.Image { | |
|
|
||
| // Use a box because `SkImage` may be deleted either due to this object | ||
| // being garbage-collected, or by an explicit call to [delete]. | ||
| late final SkiaObjectBox box; | ||
| late final RefCountedSkiaObjectBox box; | ||
|
|
||
| CkImage(this.skImage) { | ||
| box = SkiaObjectBox(this, skImage as SkDeletable); | ||
| CkImage(SkImage skImage) : this._(skImage, null); | ||
|
|
||
| CkImage._(this.skImage, RefCountedSkiaObjectBox? boxToClone) { | ||
| if (boxToClone != null) { | ||
| box = boxToClone.clone(this); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same comment as in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added these asserts |
||
| } else { | ||
| box = RefCountedSkiaObjectBox(this, skImage as SkDeletable); | ||
| } | ||
| } | ||
|
|
||
| @override | ||
|
|
@@ -126,13 +142,16 @@ class CkImage implements ui.Image { | |
| } | ||
|
|
||
| @override | ||
| ui.Image clone() => this; | ||
| ui.Image clone() => CkImage._(skImage, box); | ||
|
|
||
| @override | ||
| bool isCloneOf(ui.Image other) => other == this; | ||
| bool isCloneOf(ui.Image other) { | ||
| return other is CkImage | ||
| && other.skImage == skImage; | ||
| } | ||
|
|
||
| @override | ||
| List<StackTrace>? debugGetOpenHandleStackTraces() => null; | ||
| List<StackTrace>? debugGetOpenHandleStackTraces() => box.debugGetStackTraces(); | ||
|
|
||
| @override | ||
| int get width => skImage.width(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -282,6 +282,72 @@ class SkiaObjectBox { | |
| } | ||
| } | ||
|
|
||
| /// Uses reference counting to manage the lifecycle of a Skia object owned by a | ||
| /// wrapper object. | ||
| /// | ||
| /// When the wrapper is garbage collected, decrements the refcount (only in | ||
| /// browsers that support weak references). | ||
| /// | ||
| /// The [delete] method can be used to eagerly decrement the refcount before the | ||
| /// wrapper is garbage collected. | ||
| /// | ||
| /// The [delete] method may be called any number of times. The box | ||
| /// will only delete the object once. | ||
| class RefCountedSkiaObjectBox extends SkiaObjectBox { | ||
|
||
| RefCountedSkiaObjectBox(Object wrapper, SkDeletable skObject) | ||
| : this._(wrapper, skObject, <RefCountedSkiaObjectBox>{}); | ||
|
|
||
| RefCountedSkiaObjectBox._(Object wrapper, SkDeletable skObject, this._refs) | ||
| : super(wrapper, skObject) { | ||
| _refs.add(this); | ||
| if (assertionsEnabled) { | ||
| _debugStackTrace = StackTrace.current; | ||
| } | ||
| } | ||
|
|
||
| /// Reference handles to the same underlying [skObject]. | ||
| final Set<RefCountedSkiaObjectBox> _refs; | ||
|
|
||
| late final StackTrace? _debugStackTrace; | ||
| /// If asserts are enabled, the [StackTrace]s representing when a reference | ||
| /// was created. | ||
| List<StackTrace>? debugGetStackTraces() { | ||
| if (assertionsEnabled) { | ||
| return _refs | ||
| .map<StackTrace>((RefCountedSkiaObjectBox box) => box._debugStackTrace!) | ||
| .toList(); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| RefCountedSkiaObjectBox clone(Object wrapper) { | ||
| assert(!_isDeleted, 'Cannot clone from a deleted handle.'); | ||
| assert(_refs.isNotEmpty); | ||
| return RefCountedSkiaObjectBox._(wrapper, skObject, _refs); | ||
| } | ||
|
|
||
| /// Removes the reference count for the [skObject]. | ||
|
||
| /// | ||
| /// Does nothing if the object has already been deleted. | ||
| /// | ||
| /// If this causes the reference count to drop to zero, deletes the | ||
| /// [skObject]. | ||
| @override | ||
| void delete() { | ||
| if (_isDeleted) { | ||
| assert(!_refs.contains(this)); | ||
| return; | ||
| } | ||
| final bool removed = _refs.remove(this); | ||
| assert(removed); | ||
| _isDeleted = true; | ||
| if (_refs.isEmpty) { | ||
| _skObjectDeleteQueue.add(skObject); | ||
| _skObjectCollector ??= _scheduleSkObjectCollection(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Singleton that manages the lifecycles of [SkiaObject] instances. | ||
| class SkiaObjects { | ||
| @visibleForTesting | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we assert that the image in
boxToCloneand_skAnimatedImageare the same?Alternatively, we could split this into two specialized constructors.