blob: 5c558d5fda50a036fe67abd9cd12c21c8b07eca9 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.12
part of engine;
/// Instantiates a [ui.Codec] backed by an `SkAnimatedImage` from Skia.
void skiaInstantiateImageCodec(Uint8List list, Callback<ui.Codec> callback,
[int? width, int? height, int? format, int? rowBytes]) {
final CkAnimatedImage codec = CkAnimatedImage.decodeFromBytes(list);
callback(codec);
}
/// Instantiates a [ui.Codec] backed by an `SkAnimatedImage` from Skia after
/// requesting from URI.
Future<ui.Codec> skiaInstantiateWebImageCodec(
String uri, WebOnlyImageCodecChunkCallback? chunkCallback) {
Completer<ui.Codec> completer = Completer<ui.Codec>();
//TODO: Switch to using MakeImageFromCanvasImageSource when animated images are supported.
html.HttpRequest.request(uri, 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 CkAnimatedImage codec = CkAnimatedImage.decodeFromBytes(list);
completer.complete(codec);
}, onError: (dynamic error) {
completer.completeError(error);
});
return completer.future;
}
/// The CanvasKit implementation of [ui.Codec].
///
/// Wraps `SkAnimatedImage`.
class CkAnimatedImage implements ui.Codec, StackTraceDebugger {
/// Decodes an image from a list of encoded bytes.
CkAnimatedImage.decodeFromBytes(Uint8List bytes) {
if (assertionsEnabled) {
_debugStackTrace = StackTrace.current;
}
final SkAnimatedImage skAnimatedImage =
canvasKit.MakeAnimatedImageFromEncoded(bytes);
box = SkiaObjectBox<CkAnimatedImage, SkAnimatedImage>(this, skAnimatedImage);
}
// Use a box because `CkAnimatedImage` may be deleted either due to this
// object being garbage-collected, or by an explicit call to [dispose].
late final SkiaObjectBox<CkAnimatedImage, SkAnimatedImage> box;
@override
StackTrace get debugStackTrace => _debugStackTrace!;
StackTrace? _debugStackTrace;
bool _disposed = false;
bool get debugDisposed => _disposed;
bool _debugCheckIsNotDisposed() {
assert(!_disposed, 'This image has been disposed.');
return true;
}
@override
void dispose() {
assert(
!_disposed,
'Cannot dispose a codec that has already been disposed.',
);
_disposed = true;
// This image is no longer usable. Bump the ref count.
box.unref(this);
}
@override
int get frameCount {
assert(_debugCheckIsNotDisposed());
return box.skiaObject.getFrameCount();
}
@override
int get repetitionCount {
assert(_debugCheckIsNotDisposed());
return box.skiaObject.getRepetitionCount();
}
@override
Future<ui.FrameInfo> getNextFrame() {
assert(_debugCheckIsNotDisposed());
final int durationMillis = box.skiaObject.decodeNextFrame();
final Duration duration = Duration(milliseconds: durationMillis);
final CkImage image = CkImage(box.skiaObject.getCurrentFrame());
return Future<ui.FrameInfo>.value(AnimatedImageFrameInfo(duration, image));
}
}
/// A [ui.Image] backed by an `SkImage` from Skia.
class CkImage implements ui.Image, StackTraceDebugger {
CkImage(SkImage skImage) {
if (assertionsEnabled) {
_debugStackTrace = StackTrace.current;
}
box = SkiaObjectBox<CkImage, SkImage>(this, skImage);
}
CkImage.cloneOf(this.box) {
if (assertionsEnabled) {
_debugStackTrace = StackTrace.current;
}
box.ref(this);
}
@override
StackTrace get debugStackTrace => _debugStackTrace!;
StackTrace? _debugStackTrace;
// 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<CkImage, SkImage> box;
/// The underlying Skia image object.
///
/// Do not store the returned value. It is memory-managed by [SkiaObjectBox].
/// Storing it may result in use-after-free bugs.
SkImage get skImage => box.skiaObject;
bool _disposed = false;
bool _debugCheckIsNotDisposed() {
assert(!_disposed, 'This image has been disposed.');
return true;
}
@override
void dispose() {
assert(
!_disposed,
'Cannot dispose an image that has already been disposed.',
);
_disposed = true;
box.unref(this);
}
@override
bool get debugDisposed {
if (assertionsEnabled) {
return _disposed;
}
throw StateError(
'Image.debugDisposed is only available when asserts are enabled.');
}
@override
ui.Image clone() {
assert(_debugCheckIsNotDisposed());
return CkImage.cloneOf(box);
}
@override
bool isCloneOf(ui.Image other) {
assert(_debugCheckIsNotDisposed());
return other is CkImage && other.skImage.isAliasOf(skImage);
}
@override
List<StackTrace>? debugGetOpenHandleStackTraces() =>
box.debugGetStackTraces();
@override
int get width {
assert(_debugCheckIsNotDisposed());
return skImage.width();
}
@override
int get height {
assert(_debugCheckIsNotDisposed());
return skImage.height();
}
@override
Future<ByteData> toByteData(
{ui.ImageByteFormat format = ui.ImageByteFormat.rawRgba}) {
assert(_debugCheckIsNotDisposed());
Uint8List bytes;
if (format == ui.ImageByteFormat.rawRgba) {
final SkImageInfo imageInfo = SkImageInfo(
alphaType: canvasKit.AlphaType.Premul,
colorType: canvasKit.ColorType.RGBA_8888,
colorSpace: SkColorSpaceSRGB,
width: width,
height: height,
);
bytes = skImage.readPixels(imageInfo, 0, 0);
} else {
final SkData skData = skImage.encodeToData(); //defaults to PNG 100%
// make a copy that we can return
bytes = Uint8List.fromList(canvasKit.getDataBytes(skData));
skData.delete();
}
final ByteData data = bytes.buffer.asByteData(0, bytes.length);
return Future<ByteData>.value(data);
}
@override
String toString() {
assert(_debugCheckIsNotDisposed());
return '[$width\u00D7$height]';
}
}
/// Data for a single frame of an animated image.
class AnimatedImageFrameInfo implements ui.FrameInfo {
final Duration _duration;
final CkImage _image;
AnimatedImageFrameInfo(this._duration, this._image);
@override
Duration get duration => _duration;
@override
ui.Image get image => _image;
}