@@ -8,20 +8,17 @@ part of engine;
88/// Instantiates a [ui.Codec] backed by an `SkAnimatedImage` from Skia.
99void skiaInstantiateImageCodec (Uint8List list, Callback <ui.Codec > callback,
1010 [int ? width, int ? height, int ? format, int ? rowBytes]) {
11- final SkAnimatedImage skAnimatedImage =
12- canvasKit.MakeAnimatedImageFromEncoded (list);
13- final CkAnimatedImage animatedImage = CkAnimatedImage (skAnimatedImage);
14- final CkAnimatedImageCodec codec = CkAnimatedImageCodec (animatedImage);
11+ final CkAnimatedImage codec = CkAnimatedImage .decodeFromBytes (list);
1512 callback (codec);
1613}
1714
1815/// Instantiates a [ui.Codec] backed by an `SkAnimatedImage` from Skia after
1916/// requesting from URI.
2017Future <ui.Codec > skiaInstantiateWebImageCodec (
21- String src , WebOnlyImageCodecChunkCallback ? chunkCallback) {
18+ String uri , WebOnlyImageCodecChunkCallback ? chunkCallback) {
2219 Completer <ui.Codec > completer = Completer <ui.Codec >();
2320 //TODO: Switch to using MakeImageFromCanvasImageSource when animated images are supported.
24- html.HttpRequest .request (src , responseType: "arraybuffer" ,
21+ html.HttpRequest .request (uri , responseType: "arraybuffer" ,
2522 onProgress: (html.ProgressEvent event) {
2623 if (event.lengthComputable) {
2724 chunkCallback? .call (event.loaded! , event.total! );
@@ -33,134 +30,123 @@ Future<ui.Codec> skiaInstantiateWebImageCodec(
3330 }
3431 final Uint8List list =
3532 new Uint8List .view ((response.response as ByteBuffer ));
36- final SkAnimatedImage skAnimatedImage =
37- canvasKit.MakeAnimatedImageFromEncoded (list);
38- final CkAnimatedImage animatedImage = CkAnimatedImage (skAnimatedImage);
39- final CkAnimatedImageCodec codec = CkAnimatedImageCodec (animatedImage);
33+ final CkAnimatedImage codec = CkAnimatedImage .decodeFromBytes (list);
4034 completer.complete (codec);
4135 }, onError: (dynamic error) {
4236 completer.completeError (error);
4337 });
4438 return completer.future;
4539}
4640
47- /// A wrapper for `SkAnimatedImage` .
48- class CkAnimatedImage implements ui.Image {
49- // Use a box because `SkImage` may be deleted either due to this object
50- // being garbage-collected, or by an explicit call to [delete].
51- late final SkiaObjectBox <SkAnimatedImage > box;
41+ /// The CanvasKit implementation of [ui.Codec] .
42+ ///
43+ /// Wraps `SkAnimatedImage` .
44+ class CkAnimatedImage implements ui.Codec , StackTraceDebugger {
45+ /// Decodes an image from a list of encoded bytes.
46+ CkAnimatedImage .decodeFromBytes (Uint8List bytes) {
47+ if (assertionsEnabled) {
48+ _debugStackTrace = StackTrace .current;
49+ }
50+ final SkAnimatedImage skAnimatedImage =
51+ canvasKit.MakeAnimatedImageFromEncoded (bytes);
52+ box = SkiaObjectBox <CkAnimatedImage , SkAnimatedImage >(this , skAnimatedImage);
53+ }
5254
53- SkAnimatedImage get _skAnimatedImage => box.skObject;
55+ // Use a box because `CkAnimatedImage` may be deleted either due to this
56+ // object being garbage-collected, or by an explicit call to [dispose].
57+ late final SkiaObjectBox <CkAnimatedImage , SkAnimatedImage > box;
5458
55- CkAnimatedImage (SkAnimatedImage skAnimatedImage) {
56- box = SkiaObjectBox <SkAnimatedImage >(this , skAnimatedImage);
57- }
59+ @override
60+ StackTrace get debugStackTrace => _debugStackTrace! ;
61+ StackTrace ? _debugStackTrace;
62+
63+ bool _disposed = false ;
64+ bool get debugDisposed => _disposed;
5865
59- CkAnimatedImage .cloneOf (SkiaObjectBox <SkAnimatedImage > boxToClone) {
60- box = boxToClone.clone (this );
66+ bool _debugCheckIsNotDisposed () {
67+ assert (! _disposed, 'This image has been disposed.' );
68+ return true ;
6169 }
6270
63- bool _disposed = false ;
6471 @override
6572 void dispose () {
66- box.delete ();
73+ assert (
74+ ! _disposed,
75+ 'Cannot dispose a codec that has already been disposed.' ,
76+ );
6777 _disposed = true ;
78+
79+ // This image is no longer usable. Bump the ref count.
80+ box.unref (this );
6881 }
6982
7083 @override
71- bool get debugDisposed {
72- if (assertionsEnabled) {
73- return _disposed;
74- }
75- throw StateError (
76- 'Image.debugDisposed is only available when asserts are enabled.' );
84+ int get frameCount {
85+ assert (_debugCheckIsNotDisposed ());
86+ return box.skiaObject.getFrameCount ();
7787 }
7888
79- ui.Image clone () => CkAnimatedImage .cloneOf (box);
80-
8189 @override
82- bool isCloneOf (ui. Image other) {
83- return other is CkAnimatedImage &&
84- other._skAnimatedImage. isAliasOf (_skAnimatedImage );
90+ int get repetitionCount {
91+ assert ( _debugCheckIsNotDisposed ());
92+ return box.skiaObject. getRepetitionCount ( );
8593 }
8694
8795 @override
88- List <StackTrace >? debugGetOpenHandleStackTraces () =>
89- box.debugGetStackTraces ();
90-
91- int get frameCount => _skAnimatedImage.getFrameCount ();
92-
93- /// Decodes the next frame and returns the frame duration.
94- Duration decodeNextFrame () {
95- final int durationMillis = _skAnimatedImage.decodeNextFrame ();
96- return Duration (milliseconds: durationMillis);
96+ Future <ui.FrameInfo > getNextFrame () {
97+ assert (_debugCheckIsNotDisposed ());
98+ final int durationMillis = box.skiaObject.decodeNextFrame ();
99+ final Duration duration = Duration (milliseconds: durationMillis);
100+ final CkImage image = CkImage (box.skiaObject.getCurrentFrame ());
101+ return Future <ui.FrameInfo >.value (AnimatedImageFrameInfo (duration, image));
97102 }
103+ }
98104
99- int get repetitionCount => _skAnimatedImage.getRepetitionCount ();
100-
101- CkImage get currentFrameAsImage {
102- return CkImage (_skAnimatedImage.getCurrentFrame ());
105+ /// A [ui.Image] backed by an `SkImage` from Skia.
106+ class CkImage implements ui.Image , StackTraceDebugger {
107+ CkImage (SkImage skImage) {
108+ if (assertionsEnabled) {
109+ _debugStackTrace = StackTrace .current;
110+ }
111+ box = SkiaObjectBox <CkImage , SkImage >(this , skImage);
103112 }
104113
105- @override
106- int get width => _skAnimatedImage.width ();
107-
108- @override
109- int get height => _skAnimatedImage.height ();
110-
111- @override
112- Future <ByteData > toByteData (
113- {ui.ImageByteFormat format = ui.ImageByteFormat .rawRgba}) {
114- Uint8List bytes;
115-
116- if (format == ui.ImageByteFormat .rawRgba) {
117- final SkImageInfo imageInfo = SkImageInfo (
118- alphaType: canvasKit.AlphaType .Premul ,
119- colorType: canvasKit.ColorType .RGBA_8888 ,
120- colorSpace: SkColorSpaceSRGB ,
121- width: width,
122- height: height,
123- );
124- bytes = _skAnimatedImage.readPixels (imageInfo, 0 , 0 );
125- } else {
126- // Defaults to PNG 100%.
127- final SkData skData = _skAnimatedImage.encodeToData ();
128- // Make a copy that we can return.
129- bytes = Uint8List .fromList (canvasKit.getDataBytes (skData));
114+ CkImage .cloneOf (this .box) {
115+ if (assertionsEnabled) {
116+ _debugStackTrace = StackTrace .current;
130117 }
131-
132- final ByteData data = bytes.buffer.asByteData (0 , bytes.length);
133- return Future <ByteData >.value (data);
118+ box.ref (this );
134119 }
135120
136121 @override
137- String toString () => '[$ width \u 00D7$ height ]' ;
138- }
122+ StackTrace get debugStackTrace => _debugStackTrace ! ;
123+ StackTrace ? _debugStackTrace;
139124
140- /// A [ui.Image] backed by an `SkImage` from Skia.
141- class CkImage implements ui.Image {
142125 // Use a box because `SkImage` may be deleted either due to this object
143126 // being garbage-collected, or by an explicit call to [delete].
144- late final SkiaObjectBox <SkImage > box;
127+ late final SkiaObjectBox <CkImage , SkImage > box;
145128
146- SkImage get skImage => box.skObject;
129+ /// The underlying Skia image object.
130+ ///
131+ /// Do not store the returned value. It is memory-managed by [SkiaObjectBox] .
132+ /// Storing it may result in use-after-free bugs.
133+ SkImage get skImage => box.skiaObject;
147134
148- CkImage (SkImage skImage) {
149- box = SkiaObjectBox <SkImage >(this , skImage);
150- }
135+ bool _disposed = false ;
151136
152- CkImage .cloneOf (SkiaObjectBox <SkImage > boxToClone) {
153- box = boxToClone.clone (this );
137+ bool _debugCheckIsNotDisposed () {
138+ assert (! _disposed, 'This image has been disposed.' );
139+ return true ;
154140 }
155141
156- bool _disposed = false ;
157142 @override
158143 void dispose () {
159- box.delete ();
160- assert (() {
161- _disposed = true ;
162- return true ;
163- }());
144+ assert (
145+ ! _disposed,
146+ 'Cannot dispose an image that has already been disposed.' ,
147+ );
148+ _disposed = true ;
149+ box.unref (this );
164150 }
165151
166152 @override
@@ -173,10 +159,14 @@ class CkImage implements ui.Image {
173159 }
174160
175161 @override
176- ui.Image clone () => CkImage .cloneOf (box);
162+ ui.Image clone () {
163+ assert (_debugCheckIsNotDisposed ());
164+ return CkImage .cloneOf (box);
165+ }
177166
178167 @override
179168 bool isCloneOf (ui.Image other) {
169+ assert (_debugCheckIsNotDisposed ());
180170 return other is CkImage && other.skImage.isAliasOf (skImage);
181171 }
182172
@@ -185,14 +175,21 @@ class CkImage implements ui.Image {
185175 box.debugGetStackTraces ();
186176
187177 @override
188- int get width => skImage.width ();
178+ int get width {
179+ assert (_debugCheckIsNotDisposed ());
180+ return skImage.width ();
181+ }
189182
190183 @override
191- int get height => skImage.height ();
184+ int get height {
185+ assert (_debugCheckIsNotDisposed ());
186+ return skImage.height ();
187+ }
192188
193189 @override
194190 Future <ByteData > toByteData (
195191 {ui.ImageByteFormat format = ui.ImageByteFormat .rawRgba}) {
192+ assert (_debugCheckIsNotDisposed ());
196193 Uint8List bytes;
197194
198195 if (format == ui.ImageByteFormat .rawRgba) {
@@ -208,38 +205,17 @@ class CkImage implements ui.Image {
208205 final SkData skData = skImage.encodeToData (); //defaults to PNG 100%
209206 // make a copy that we can return
210207 bytes = Uint8List .fromList (canvasKit.getDataBytes (skData));
208+ skData.delete ();
211209 }
212210
213211 final ByteData data = bytes.buffer.asByteData (0 , bytes.length);
214212 return Future <ByteData >.value (data);
215213 }
216214
217215 @override
218- String toString () => '[$width \u 00D7$height ]' ;
219- }
220-
221- /// A [Codec] that wraps an `SkAnimatedImage` .
222- class CkAnimatedImageCodec implements ui.Codec {
223- CkAnimatedImage animatedImage;
224-
225- CkAnimatedImageCodec (this .animatedImage);
226-
227- @override
228- void dispose () {
229- animatedImage.dispose ();
230- }
231-
232- @override
233- int get frameCount => animatedImage.frameCount;
234-
235- @override
236- int get repetitionCount => animatedImage.repetitionCount;
237-
238- @override
239- Future <ui.FrameInfo > getNextFrame () {
240- final Duration duration = animatedImage.decodeNextFrame ();
241- final CkImage image = animatedImage.currentFrameAsImage;
242- return Future <ui.FrameInfo >.value (AnimatedImageFrameInfo (duration, image));
216+ String toString () {
217+ assert (_debugCheckIsNotDisposed ());
218+ return '[$width \u 00D7$height ]' ;
243219 }
244220}
245221
0 commit comments